cms

package
v1.6.24 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: AGPL-3.0 Imports: 30 Imported by: 0

Documentation

Overview

Package cms provides services for Content Management System functionality

Index

Constants

View Source
const (
	DraftReviewApproved         = "APPROVED"
	DraftReviewChangesRequested = "CHANGES_REQUESTED"

	// DraftReviewGrantLifetime bounds every review grant. Grants are cheap,
	// ephemeral assignments refreshed on re-share; the bound prevents a stale
	// grant from authorizing reviewer reads, URL minting, or approval forever.
	DraftReviewGrantLifetime = 7 * 24 * time.Hour

	// Bound-media blocking reasons exposed through the review state surface.
	DraftReviewMediaReasonMissing     = "BOUND_MEDIA_MISSING"
	DraftReviewMediaReasonNotReady    = "BOUND_MEDIA_NOT_READY"
	DraftReviewMediaReasonWithdrawn   = "BOUND_MEDIA_WITHDRAWN"
	DraftReviewMediaReasonSuperseded  = "BOUND_MEDIA_SUPERSEDED"
	DraftReviewMediaReasonUnavailable = "BOUND_MEDIA_UNAVAILABLE"
)

Draft review verdict values.

View Source
const (
	DraftStatusDraft      = "draft"
	DraftStatusScheduled  = "scheduled"
	DraftStatusPublishing = "publishing"
	DraftStatusPublished  = "published"
	DraftStatusFailed     = "failed"
)

Draft status values. They are exported as a single source of truth: the CMS publish lifecycle writes them and the media-side orphan reconciliation enumerates failed and stale-publishing drafts, so a duplicated constant in pkg/services would drift.

View Source
const (
	LegacyArticleSkipNil               = "nil_article"
	LegacyArticleSkipNotArticle        = "not_article"
	LegacyArticleSkipMissingID         = "missing_id"
	LegacyArticleSkipAlreadyCanonical  = "already_canonical_article"
	LegacyArticleSkipNotLegacyObjectID = "not_legacy_objects_id"
	LegacyArticleSkipMissingSlug       = "missing_slug"
	LegacyArticleConflictDuplicate     = "duplicate_legacy_alias"
	LegacyArticleConflictOccupied      = "canonical_alias_occupied"
)

Legacy Article migration skip and conflict reason codes.

View Source
const (
	PromoPackageReviewReasonApprovalRequired  = "REVIEW_APPROVAL_REQUIRED"
	PromoPackageReviewReasonPrincipalRequired = "PRINCIPAL_APPROVAL_REQUIRED"
	PromoPackageReviewReasonPrincipalMissing  = "PRINCIPAL_APPROVAL_UNAVAILABLE"
	PromoPackageReviewReasonAssetMissing      = "ASSET_MISSING"
	PromoPackageReviewReasonAssetNotOwned     = "ASSET_NOT_OWNED"
	PromoPackageReviewReasonAssetNotPublished = "ASSET_NOT_PUBLISHED"
	PromoPackageReviewReasonAssetDigestChange = "ASSET_DIGEST_CHANGED"
	PromoPackageReviewReasonReleased          = "PACKAGE_RELEASED"
	PromoPackageReviewReasonReleasing         = "PACKAGE_RELEASING"
)

Promo package blocking reasons exposed through the review state surface, mirroring the draft-review vocabulary. The ASSET_* reasons name exactly why the exact approved bytes cannot be released.

Variables

View Source
var (
	// ErrDraftReviewApprovalRequired means a required reviewer is missing a
	// current approval for the exact reviewed content. Required reviewers are
	// holders of an active grant plus every reviewer who ever recorded a verdict
	// (operator doctrine, "requested = required").
	ErrDraftReviewApprovalRequired = errors.New("draft requires approval from every required reviewer")
	// ErrDraftReviewPrincipalApprovalRequired means the operator doctrine
	// principal floor is unmet: the releasing actor is not the instance
	// principal, and the principal does not hold a current approving verdict
	// (regardless of draft provenance).
	ErrDraftReviewPrincipalApprovalRequired = errors.New("draft release requires an active approval from the instance principal")
	// ErrDraftReviewMediaRequired means a required bound asset cannot serve the
	// exact approved bytes (missing, not ready, withdrawn, superseded, or
	// unavailable). The wrapped message names the blocking reasons.
	ErrDraftReviewMediaRequired = errors.New("draft requires its bound media to be ready and available")
	// ErrDraftReviewConflict is the additive conflict signal surfaced when a
	// review submit carries an expected content hash that no longer matches the
	// stored draft; the caller re-reads and retries.
	ErrDraftReviewConflict = errors.New("draft changed concurrently")
	// ErrDraftReviewReviewContentChanged is the additive conflict signal surfaced
	// when a review submit carries an expected content hash that no longer
	// matches the stored draft: the owner edited after the reviewer inspected
	// the draft, so the verdict must not bless unseen content.
	ErrDraftReviewReviewContentChanged = errors.New("draft content changed since the reviewer inspected it")
	// ErrInstancePrincipalUnavailable means the principal provider could not be used.
	ErrInstancePrincipalUnavailable = errors.New("instance principal is unavailable")
	// ErrInstancePrincipalNotConfigured means the provider returned no principal username.
	ErrInstancePrincipalNotConfigured = errors.New("instance principal is not configured")
)
View Source
var (
	// ErrPromoPackageApprovalRequired means a required reviewer is missing a
	// current approval for the exact reviewed package content.
	ErrPromoPackageApprovalRequired = errors.New("promo package requires approval from every required reviewer")
	// ErrPromoPackagePrincipalApprovalRequired means the release gate's operator
	// doctrine floor is unmet: the releasing actor is not the instance
	// principal, and the principal does not hold a current approving verdict
	// (regardless of asset provenance).
	ErrPromoPackagePrincipalApprovalRequired = errors.New("promo package release requires an active approval from the instance principal")
	// ErrPromoPackageAssetUnavailable means a bound asset cannot serve the exact
	// approved bytes (missing, not owned, not in the PUBLISHED durable state, or
	// its digest changed after review). The wrapped message names the reasons.
	ErrPromoPackageAssetUnavailable = errors.New("promo package asset cannot serve the exact approved bytes")
	// ErrPromoPackageAlreadyReleased means the release transition already
	// stamped an outbound Status; re-release and post-release composition are
	// refused.
	ErrPromoPackageAlreadyReleased = errors.New("promo package is already released")
	// ErrPromoPackageConflict is the additive conflict signal surfaced when the
	// version-conditioned content or release write loses a concurrent update;
	// the caller re-reads and retries.
	ErrPromoPackageConflict = errors.New("promo package changed concurrently")
	// ErrPromoPackageReviewContentChanged is the additive conflict signal
	// surfaced when a review submit carries an expected content hash that no
	// longer matches the stored package: the owner recomposed after the reviewer
	// inspected the package, so the verdict must not bless unseen content.
	ErrPromoPackageReviewContentChanged = errors.New("promo package content changed since the reviewer inspected it")
	// ErrPromoPackageReleaseInProgress means the package holds the transient
	// releasing reservation (a previous release reserved it but never stamped
	// an outbound Status, or is mid-flight). Release and composition are
	// refused until an operator reconciles the reservation; the reservation
	// guarantees the loser of a concurrent release never creates a post.
	ErrPromoPackageReleaseInProgress = errors.New("promo package release is already in progress")
)

Functions

func DraftReviewContentHash added in v1.6.4

func DraftReviewContentHash(d *models.Draft) string

DraftReviewContentHash returns the canonical text hash used to bind review verdicts to draft content when no media digest resolution is available (equivalent to a draft with no bound media).

func DraftReviewContentHashWithMedia added in v1.6.23

func DraftReviewContentHashWithMedia(d *models.Draft, mediaDigests map[string]string) string

DraftReviewContentHashWithMedia binds review verdicts to the exact bound media bytes. mediaDigests maps each bound MediaID to its canonical sha256:<hex> digest; callers resolve digests through the editorial media repository (bounded at 100 usages per draft).

func RenderDraftPreview added in v1.4.9

func RenderDraftPreview(draft *models.Draft) (cmsrender.RenderedArticleContent, error)

RenderDraftPreview renders draft source through the canonical Article renderer.

Types

type ArticleService

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

ArticleService handles business logic for articles

func NewArticleService

func NewArticleService(
	articleRepo articleServiceRepository,
	actorRepo actorRepository,
	seriesRepo cmsSeriesArticleCountUpdater,
	categoryRepo cmsCategoryArticleCountUpdater,
	revisionService articleRevisionCreator,
	federation FederationService,
	logger *zap.Logger,
) *ArticleService

NewArticleService creates a new ArticleService

func (*ArticleService) CreateArticle

func (s *ArticleService) CreateArticle(ctx context.Context, article *models.Article) error

CreateArticle creates a new article

func (*ArticleService) DeleteArticle

func (s *ArticleService) DeleteArticle(ctx context.Context, article *models.Article) error

DeleteArticle deletes an article and federates a Delete activity best-effort.

func (*ArticleService) GetArticle

func (s *ArticleService) GetArticle(ctx context.Context, articleID string) (*models.Article, error)

GetArticle retrieves an article by ID.

func (*ArticleService) GetArticleBySlug

func (s *ArticleService) GetArticleBySlug(ctx context.Context, slug string) (*models.Article, error)

GetArticleBySlug retrieves an article by its slug index (no legacy fallback).

func (*ArticleService) GetArticleByTenantSlug added in v1.2.53

func (s *ArticleService) GetArticleByTenantSlug(ctx context.Context, tenant string, slug string) (*models.Article, error)

GetArticleByTenantSlug retrieves an article by a tenant-scoped slug index with legacy global-index compatibility. Legacy matches are only returned after the resolved article ID is verified to belong to the requested tenant.

func (*ArticleService) UpdateArticle

func (s *ArticleService) UpdateArticle(ctx context.Context, article *models.Article) error

UpdateArticle updates an existing article and records a revision if configured.

type CategoryService

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

CategoryService handles business logic for categories

func NewCategoryService

func NewCategoryService(categoryRepo categoryRepository, logger *zap.Logger) *CategoryService

NewCategoryService creates a new CategoryService

func (*CategoryService) CreateCategory

func (s *CategoryService) CreateCategory(ctx context.Context, category *models.Category) error

CreateCategory creates a new category

func (*CategoryService) DeleteCategory

func (s *CategoryService) DeleteCategory(ctx context.Context, id string) error

DeleteCategory deletes a category

func (*CategoryService) GetCategory

func (s *CategoryService) GetCategory(ctx context.Context, id string) (*models.Category, error)

GetCategory retrieves a category by ID

func (*CategoryService) ListCategories

func (s *CategoryService) ListCategories(ctx context.Context, parentID *string, limit int) ([]*models.Category, error)

ListCategories lists categories

func (*CategoryService) UpdateCategory

func (s *CategoryService) UpdateCategory(ctx context.Context, category *models.Category) error

UpdateCategory updates an existing category

type DraftEditorialMediaBinding added in v1.6.23

type DraftEditorialMediaBinding struct {
	Usage models.DraftMediaUsage
	Media *models.Media
}

DraftEditorialMediaBinding resolves one modeled usage without hiding a missing asset. Preview clients need the nil Media value to render a conspicuous missing placeholder instead of silently dropping the binding.

type DraftReviewReadState added in v1.6.4

type DraftReviewReadState struct {
	ContentHash               string
	Grants                    []*models.DraftReviewGrant
	GrantCount                int
	GrantsTruncated           bool
	CurrentVerdicts           map[string]*models.DraftReviewVerdict
	ReviewersApproved         bool
	PrincipalApprovalRequired bool
	PrincipalApproved         bool
	PublishEligible           bool
	BlockingReasons           []string
}

DraftReviewReadState is the complete, revision-bound review state exposed to authorized clients. CurrentVerdicts contains only verdicts that apply to the present draft digest and were recorded after the active grant.

type DraftService

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

DraftService handles business logic for drafts

func NewDraftService

func NewDraftService(draftRepo draftRepository, articleService *ArticleService, domain string, schedulingEnabled bool, logger *zap.Logger) *DraftService

NewDraftService creates a new DraftService

func (*DraftService) ActiveDraftReviewGrant added in v1.5.32

func (s *DraftService) ActiveDraftReviewGrant(ctx context.Context, owner, draftID, reviewer string) (*models.DraftReviewGrant, error)

ActiveDraftReviewGrant returns a non-revoked, non-expired grant for one reviewer. Expired grants fail closed: they authorize neither reviewer reads/URL minting nor the approval gate.

func (*DraftService) ActivePromoPackageReviewGrant added in v1.6.23

func (s *DraftService) ActivePromoPackageReviewGrant(ctx context.Context, owner, packageID, reviewer string) (*models.PromoReviewGrant, error)

ActivePromoPackageReviewGrant returns a non-revoked, non-expired grant for one reviewer. Expired grants fail closed.

func (*DraftService) Autosave

func (s *DraftService) Autosave(ctx context.Context, authorID string, draft *models.Draft) error

Autosave updates the draft content without changing its primary status

func (*DraftService) BoundEditorialMediaForCaller added in v1.6.23

func (s *DraftService) BoundEditorialMediaForCaller(ctx context.Context, caller, draftID, mediaID string) (*models.Media, error)

BoundEditorialMediaForCaller authorizes one exact asset against an owner or active reviewer grant. It intentionally cannot authorize unbound media.

func (*DraftService) CancelScheduledDraft

func (s *DraftService) CancelScheduledDraft(ctx context.Context, authorID, draftID string) error

CancelScheduledDraft cancels a scheduled draft publish.

func (*DraftService) ComposePromoPackage added in v1.6.23

func (s *DraftService) ComposePromoPackage(ctx context.Context, owner string, input PromoPackageComposeInput) (*models.PromoPackage, error)

ComposePromoPackage creates a new promo package or replaces the content of an existing one (PackageID set). Every content change re-hashes the package, so prior review verdicts and principal authorization go stale through the verdict-vs-hash comparison and release stays blocked until the changed package is re-reviewed and re-authorized. Only PUBLISHED assets owned by the composer can bind (structurally: any other lifecycle state is rejected), and visibility is restricted to public/unlisted (issue #1446 scope). A released package cannot be re-composed.

func (*DraftService) CountSharedDraftReviews added in v1.5.32

func (s *DraftService) CountSharedDraftReviews(ctx context.Context, reviewer string) (int, error)

CountSharedDraftReviews returns the full active queue size, applying the same active-grant predicate as SharedDraftReviews so the reported count can never exceed the edges the list would return for the same reviewer.

func (*DraftService) CreateDraft

func (s *DraftService) CreateDraft(ctx context.Context, draft *models.Draft) error

CreateDraft creates a new draft

func (*DraftService) DeleteDraft

func (s *DraftService) DeleteDraft(ctx context.Context, authorID, draftID string) error

DeleteDraft deletes a draft

func (*DraftService) DraftEditorialMediaForCaller added in v1.6.23

func (s *DraftService) DraftEditorialMediaForCaller(ctx context.Context, caller, draftID string) (*models.Draft, []DraftEditorialMediaBinding, error)

DraftEditorialMediaForCaller returns only assets bound to the authorized draft. Review grants never turn into a general media-library capability.

func (*DraftService) DraftReviewForCaller added in v1.5.32

func (s *DraftService) DraftReviewForCaller(ctx context.Context, caller, draftID string) (*models.Draft, *models.DraftReviewGrant, error)

DraftReviewForCaller resolves a draft only for its owner or active reviewer.

func (*DraftService) DraftReviewState added in v1.6.4

func (s *DraftService) DraftReviewState(ctx context.Context, owner, draftID string, draft *models.Draft) (*DraftReviewReadState, error)

DraftReviewState returns review grants, current approvals, and the same eligibility decision enforced by publish and schedule operations.

func (*DraftService) DraftReviewVerdicts added in v1.5.32

func (s *DraftService) DraftReviewVerdicts(ctx context.Context, owner, draftID string) ([]*models.DraftReviewVerdict, error)

DraftReviewVerdicts lists ordered verdict history for a draft.

func (*DraftService) GetDraft

func (s *DraftService) GetDraft(ctx context.Context, authorID, draftID string) (*models.Draft, error)

GetDraft retrieves a draft

func (*DraftService) GetPromoPackage added in v1.6.23

func (s *DraftService) GetPromoPackage(ctx context.Context, owner, packageID string) (*models.PromoPackage, error)

GetPromoPackage loads a package for its owner.

func (*DraftService) HasActiveApproval added in v1.5.32

func (s *DraftService) HasActiveApproval(ctx context.Context, owner, draftID string) (bool, error)

HasActiveApproval preserves the combined non-principal-releaser gate for callers that need both required-reviewer and principal approval.

func (*DraftService) HasPrincipalApproval added in v1.5.32

func (s *DraftService) HasPrincipalApproval(ctx context.Context, owner, draftID string) (bool, error)

HasPrincipalApproval applies the additional generated-content principal rule.

func (*DraftService) HasUnanimousActiveApproval added in v1.5.32

func (s *DraftService) HasUnanimousActiveApproval(ctx context.Context, owner, draftID string) (bool, error)

HasUnanimousActiveApproval applies the requested = required rule: every reviewer with an active grant plus every reviewer who ever recorded a verdict must hold a current approving verdict. With no grants and no verdicts the result is vacuously true, preserving human draft behavior.

func (*DraftService) ListPromoPackages added in v1.6.23

func (s *DraftService) ListPromoPackages(ctx context.Context, owner string, limit int, cursor string) ([]*models.PromoPackage, string, error)

ListPromoPackages lists one owner's promo packages, paginated.

func (*DraftService) OwnedDraftReviews added in v1.6.4

func (s *DraftService) OwnedDraftReviews(ctx context.Context, owner string) ([]*models.DraftReviewGrant, error)

OwnedDraftReviews returns active review assignments created by one draft owner. The complete active set is returned so GraphQL can filter before paginating and report an exact count.

func (*DraftService) OwnedPromoPackageReviews added in v1.6.23

func (s *DraftService) OwnedPromoPackageReviews(ctx context.Context, owner string) ([]*models.PromoReviewGrant, error)

OwnedPromoPackageReviews returns active review assignments created by one package owner.

func (*DraftService) PreviewDraft added in v1.4.9

func (s *DraftService) PreviewDraft(ctx context.Context, authorID, draftID string) (cmsrender.RenderedArticleContent, error)

PreviewDraft renders a draft through the same Article publication renderer used for ActivityPub and public HTML.

func (*DraftService) PromoPackageForCaller added in v1.6.23

func (s *DraftService) PromoPackageForCaller(ctx context.Context, caller, packageID string) (*models.PromoPackage, *models.PromoReviewGrant, error)

PromoPackageForCaller resolves a package only for its owner or an active reviewer. Pre-release packages are never world-readable.

func (*DraftService) PromoPackageReviewState added in v1.6.23

func (s *DraftService) PromoPackageReviewState(ctx context.Context, owner, packageID string, pkg *models.PromoPackage) (*PromoPackageReviewReadState, error)

PromoPackageReviewState returns review grants, current approvals, resolved asset state, and the same eligibility decision enforced by the release gate.

func (*DraftService) PromoPackageVerdicts added in v1.6.23

func (s *DraftService) PromoPackageVerdicts(ctx context.Context, owner, packageID string) ([]*models.PromoReviewVerdict, error)

PromoPackageVerdicts lists ordered verdict history for a package.

func (*DraftService) PublishDraft

func (s *DraftService) PublishDraft(ctx context.Context, authorID, draftID string) (*models.Article, error)

PublishDraft converts a draft into an article

func (*DraftService) PublishDraftWithAttribution added in v1.6.5

func (s *DraftService) PublishDraftWithAttribution(ctx context.Context, authorID, draftID, actedBy string) (*models.Article, error)

PublishDraftWithAttribution converts a draft into an article, recording actedBy (a local actor URI) on the resulting article when the publish is performed by a caller acting under an active share grant. An empty actedBy preserves the draft's own attribution.

func (*DraftService) ReleasePromoPackage added in v1.6.23

func (s *DraftService) ReleasePromoPackage(ctx context.Context, owner, packageID string) (*PromoPackageRelease, error)

ReleasePromoPackage releases an approved package: the operator doctrine gate (every required reviewer holds a current approving verdict — active grants plus ever-recorded-verdict reviewers, since revocation cannot delete a required approval — and the instance principal holds a current approval for any non-principal release, regardless of asset provenance) must be current for the exact reviewed content, and every bound asset must still be in the PUBLISHED durable state carrying the digest bound at review time. The release transition reserves the package FIRST (draft -> releasing through a version-conditioned write, so a concurrent double-release has exactly one winner and every loser conflicts before any post exists), then re-derives the approval answers from the current grants and verdicts (a revocation, expiry, or CHANGES_REQUESTED verdict landing between the gate check and the reservation win aborts the release with the reservation rolled back, exactly as if the gate had re-checked at the CAS moment), then creates the outbound Status with the exact approved assets attached (reusing the M2 published serving, no re-upload) and AI-authorship disclosure intact, then finalizes releasing -> released recording the created Status. On post-creation failure the reservation is rolled back to draft (same CAS lane); on finalize failure the created Status ID is surfaced via PromoPackageStampError so the caller cannot blindly retry into a second post. The release creates the post and nothing else (no boosts, likes, or synthetic engagement). A released package cannot release again.

func (*DraftService) RevokeDraftReview added in v1.5.32

func (s *DraftService) RevokeDraftReview(ctx context.Context, owner, draftID, reviewer string) error

RevokeDraftReview immediately disables a reviewer grant.

func (*DraftService) RevokePromoPackageReview added in v1.6.23

func (s *DraftService) RevokePromoPackageReview(ctx context.Context, owner, packageID, reviewer string) error

RevokePromoPackageReview immediately disables a reviewer grant.

func (*DraftService) ScheduleDraft

func (s *DraftService) ScheduleDraft(ctx context.Context, authorID, draftID string, scheduledAt time.Time) error

ScheduleDraft schedules a draft for publishing

func (*DraftService) SetEditorialMedia added in v1.6.23

func (s *DraftService) SetEditorialMedia(ctx context.Context, authorID, draftID string, usages []models.DraftMediaUsage) (*models.Draft, error)

SetEditorialMedia replaces the complete ordered media binding for a draft. Media changes deliberately do not extend the review content hash in M1; M2 owns byte-bound approval and publish-gate semantics.

func (*DraftService) SetEditorialMediaRepository added in v1.6.23

func (s *DraftService) SetEditorialMediaRepository(repo editorialMediaRepository)

SetEditorialMediaRepository wires the media lookup used to enforce asset ownership and internal-state invariants at the CMS service boundary.

func (*DraftService) SetEditorialPublishMinter added in v1.6.23

func (s *DraftService) SetEditorialPublishMinter(minter editorialPublishMinter)

SetEditorialPublishMinter wires the durable published-serving transition used at the publish gate. Without it, drafts with bound media cannot publish.

func (*DraftService) SetPrincipalUsernameProvider added in v1.5.32

func (s *DraftService) SetPrincipalUsernameProvider(provider func(context.Context) (string, error))

SetPrincipalUsernameProvider supplies the instance's designated operator account. Generated drafts fail closed when this provider is absent or cannot identify one.

func (*DraftService) SetPromoPackageRepository added in v1.6.23

func (s *DraftService) SetPromoPackageRepository(repo promoPackageRepository)

SetPromoPackageRepository wires the promo package persistence used by the compose, review, and release operations.

func (*DraftService) SetPromoStatusCreator added in v1.6.23

func (s *DraftService) SetPromoStatusCreator(creator promoStatusCreator)

SetPromoStatusCreator wires the outbound Status creation used by the release transition. Without it, a package with approved assets cannot release.

func (*DraftService) ShareDraftForReview added in v1.5.32

func (s *DraftService) ShareDraftForReview(ctx context.Context, owner, draftID, reviewer string) (*models.DraftReviewGrant, error)

ShareDraftForReview creates or refreshes an owner-authorized reviewer grant.

func (*DraftService) SharePromoPackageForReview added in v1.6.23

func (s *DraftService) SharePromoPackageForReview(ctx context.Context, owner, packageID, reviewer string) (*models.PromoReviewGrant, error)

SharePromoPackageForReview creates or refreshes an owner-authorized reviewer grant (7-day bound expiry, fail-closed, matching the draft-review posture).

func (*DraftService) SharedDraftReviews added in v1.5.32

func (s *DraftService) SharedDraftReviews(ctx context.Context, reviewer string, limit int, cursor string) ([]*models.DraftReviewGrant, string, error)

SharedDraftReviews lists one cursor page of active review queue grants. Expired grants are excluded so a stale assignment cannot appear actionable.

func (*DraftService) SharedPromoPackageReviews added in v1.6.23

func (s *DraftService) SharedPromoPackageReviews(ctx context.Context, reviewer string, limit int, cursor string) ([]*models.PromoReviewGrant, string, error)

SharedPromoPackageReviews lists one cursor page of active review queue grants.

func (*DraftService) SubmitDraftReview added in v1.5.32

func (s *DraftService) SubmitDraftReview(ctx context.Context, caller, owner, draftID, verdict, notes string, expectedContentHashes ...string) (*models.DraftReviewVerdict, error)

SubmitDraftReview records an immutable reviewer verdict bound to the exact draft content hash. The caller MAY carry the expectedContentHash it actually inspected: an empty value is the legacy no-constraint path (the draft surface has deployed consumers that submit without a hash, so the argument defaults to empty and the advisory binding is closed at the client contract rather than the server). When a non-empty expected hash no longer matches the stored draft (the owner edited between the reviewer's read and this submit), the submit is rejected with a conflict signal instead of silently blessing unseen content.

func (*DraftService) SubmitPromoPackageReview added in v1.6.23

func (s *DraftService) SubmitPromoPackageReview(ctx context.Context, caller, owner, packageID, verdict, notes, expectedContentHash string) (*models.PromoReviewVerdict, error)

SubmitPromoPackageReview records an immutable reviewer verdict bound to the exact package content hash. The caller carries the expectedContentHash it actually inspected — the argument is required: an empty value is rejected so the advisory-binding gap (empty → no constraint) cannot exist beneath the GraphQL contract, which declares contentHash non-null. When the stored package no longer matches the inspected hash (the owner recomposed between the reviewer's read and this submit), the submit is rejected with a conflict signal instead of silently blessing unseen content.

func (*DraftService) UpdateDraft

func (s *DraftService) UpdateDraft(ctx context.Context, authorID string, draft *models.Draft) error

UpdateDraft updates an existing draft

type EditorialPublishedMedia added in v1.6.23

type EditorialPublishedMedia struct {
	MediaID     string
	ContentHash string
	ContentType string
	FileSize    int64
	Width       int
	Height      int
	URL         string
	S3Key       string
	PublishedAt time.Time
}

EditorialPublishedMedia is the durable public serving minted for one bound asset at the publish transition. The URL serves the exact approved original bytes without expiring presignatures or temporary generator URLs.

type FederationService

type FederationService interface {
	DeliverToFollowers(ctx context.Context, activity *activitypub.Activity, actor *activitypub.Actor) error
	DeliverToRecipients(ctx context.Context, activity *activitypub.Activity, actor *activitypub.Actor) error
}

FederationService interface to avoid circular imports with pkg/services

type LegacyArticleMigrationCandidate added in v1.4.9

type LegacyArticleMigrationCandidate struct {
	ArticleID           string
	Tenant              string
	Slug                string
	ProposedCanonicalID string
	ProposedAliasURL    string
}

LegacyArticleMigrationCandidate maps one legacy Article to its proposed browser alias. ProposedCanonicalID intentionally remains the stored legacy Article ID for the MVP so the dry-run never creates a second ActivityPub object identity for existing content.

type LegacyArticleMigrationConflict added in v1.4.9

type LegacyArticleMigrationConflict struct {
	Tenant     string
	Slug       string
	AliasURL   string
	ArticleIDs []string
	Reason     string
}

LegacyArticleMigrationConflict describes a mapping that cannot be applied without creating duplicate alias/canonical identity ambiguity.

type LegacyArticleMigrationPlan added in v1.4.9

type LegacyArticleMigrationPlan struct {
	Candidates []LegacyArticleMigrationCandidate
	Conflicts  []LegacyArticleMigrationConflict
	Skipped    []LegacyArticleMigrationSkipped
}

LegacyArticleMigrationPlan is the dry-run output for deciding how legacy Article IDs under /objects/<uuid> could be exposed through non-authoritative /articles/<slug> aliases without rewriting ActivityPub object identity.

func PlanLegacyArticleMigration added in v1.4.9

func PlanLegacyArticleMigration(articles []*models.Article, defaultDomain string) LegacyArticleMigrationPlan

PlanLegacyArticleMigration performs a pure dry-run over Article rows. It identifies legacy /objects/<uuid> Articles, proposes non-authoritative /articles/<slug> aliases, and reports conflicts before any write path exists.

type LegacyArticleMigrationSkipped added in v1.4.9

type LegacyArticleMigrationSkipped struct {
	ArticleID string
	Reason    string
}

LegacyArticleMigrationSkipped describes an Article row that is not a legacy /objects/<uuid> Article migration candidate.

type PromoPackageComposeInput added in v1.6.23

type PromoPackageComposeInput struct {
	PackageID     string
	ArticleID     string
	PostText      string
	Visibility    string
	AssetMediaIDs []string
}

PromoPackageComposeInput carries the full reviewed content of a promo package. The same shape composes a new package (PackageID empty) or replaces the content of an existing one (PackageID set); every content change re-hashes and stales prior approvals.

type PromoPackageRelease added in v1.6.23

type PromoPackageRelease struct {
	Package          *models.PromoPackage
	ReleasedStatusID string
	StatusURL        string
}

PromoPackageRelease is the outcome of a successful release: the stamped package and the created outbound Status.

type PromoPackageResolvedAsset added in v1.6.23

type PromoPackageResolvedAsset struct {
	Binding models.PromoPackageAsset
	Media   *models.Media // nil when the binding cannot resolve
	Reason  string
}

PromoPackageResolvedAsset is one bound asset with its live media record, used by the review surface to render state and by the release gate to verify the exact approved bytes are still attachable. Reason carries the per-asset blocking reason (empty when the asset resolves to the PUBLISHED state) so the review projection can render conspicuous per-asset states.

type PromoPackageReviewReadState added in v1.6.23

type PromoPackageReviewReadState struct {
	ContentHash               string
	Grants                    []*models.PromoReviewGrant
	GrantCount                int
	GrantsTruncated           bool
	CurrentVerdicts           map[string]*models.PromoReviewVerdict
	ReviewersApproved         bool
	PrincipalApprovalRequired bool
	PrincipalApproved         bool
	PrincipalUnavailable      bool
	ResolvedAssets            []PromoPackageResolvedAsset
	ReleaseEligible           bool
	BlockingReasons           []string
}

PromoPackageReviewReadState is the complete, hash-bound review state exposed to authorized clients. CurrentVerdicts contains only verdicts that apply to the present package digest and were recorded after the active grant.

type PromoPackageStampError added in v1.6.23

type PromoPackageStampError struct {
	ReleasedStatusID string
	Err              error
}

PromoPackageStampError surfaces a release whose outbound Status WAS created but could not be stamped onto the package (the final releasing -> released write failed). The caller must NOT blindly retry — a retry would create a second post — so the created status ID is carried for operator reconciliation. The package stays in the transient releasing reservation, which blocks further release attempts until it is reconciled.

func (*PromoPackageStampError) Error added in v1.6.23

func (e *PromoPackageStampError) Error() string

func (*PromoPackageStampError) Unwrap added in v1.6.23

func (e *PromoPackageStampError) Unwrap() error

type PublicationService

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

PublicationService handles business logic for publications

func NewPublicationService

func NewPublicationService(pubRepo publicationRepository, pubMemberRepo publicationMemberRepository, logger *zap.Logger) *PublicationService

NewPublicationService creates a new PublicationService

func (*PublicationService) AddMember

func (s *PublicationService) AddMember(ctx context.Context, member *models.PublicationMember) error

AddMember adds a member to a publication

func (*PublicationService) CreatePublication

func (s *PublicationService) CreatePublication(ctx context.Context, publication *models.Publication) error

CreatePublication creates a new publication

func (*PublicationService) DeletePublication

func (s *PublicationService) DeletePublication(ctx context.Context, id string) error

DeletePublication deletes a publication

func (*PublicationService) GetMember

func (s *PublicationService) GetMember(ctx context.Context, publicationID, userID string) (*models.PublicationMember, error)

GetMember retrieves a specific member

func (*PublicationService) GetPublication

func (s *PublicationService) GetPublication(ctx context.Context, id string) (*models.Publication, error)

GetPublication retrieves a publication by ID

func (*PublicationService) ListMembers

func (s *PublicationService) ListMembers(ctx context.Context, publicationID string) ([]*models.PublicationMember, error)

ListMembers lists all members of a publication

func (*PublicationService) RemoveMember

func (s *PublicationService) RemoveMember(ctx context.Context, publicationID, userID string) error

RemoveMember removes a member from a publication

func (*PublicationService) UpdateMemberRole

func (s *PublicationService) UpdateMemberRole(ctx context.Context, publicationID, userID, role string) error

UpdateMemberRole updates a member's role

func (*PublicationService) UpdatePublication

func (s *PublicationService) UpdatePublication(ctx context.Context, publication *models.Publication) error

UpdatePublication updates an existing publication

type RevisionService

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

RevisionService handles business logic for content revisions

func NewRevisionService

func NewRevisionService(
	revisionRepo revisionRepository,
	articleRepo articleRepositoryWithDB,
	seriesRepo cmsSeriesArticleCountUpdater,
	categoryRepo cmsCategoryArticleCountUpdater,
	maxRevisionsPerObject int,
	logger *zap.Logger,
) *RevisionService

NewRevisionService creates a new RevisionService

func (*RevisionService) CreateRevision

func (s *RevisionService) CreateRevision(ctx context.Context, article *models.Article) (*models.Revision, error)

CreateRevision creates a new revision from an article state

func (*RevisionService) GetRevision

func (s *RevisionService) GetRevision(ctx context.Context, objectID string, version int) (*models.Revision, error)

GetRevision retrieves a specific revision

func (*RevisionService) ListRevisions

func (s *RevisionService) ListRevisions(ctx context.Context, objectID string, limit int) ([]*models.Revision, error)

ListRevisions lists revisions for a given article

func (*RevisionService) RestoreRevision

func (s *RevisionService) RestoreRevision(ctx context.Context, objectID string, version int) (*models.Article, error)

RestoreRevision reverts an article to a specific revision

type SeriesService

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

SeriesService handles business logic for series

func NewSeriesService

func NewSeriesService(seriesRepo seriesRepository, articleRepo articleSeriesRepository, logger *zap.Logger) *SeriesService

NewSeriesService creates a new SeriesService

func (*SeriesService) AddArticleToSeries

func (s *SeriesService) AddArticleToSeries(ctx context.Context, articleID string, seriesID string, order int) error

AddArticleToSeries adds an article to a series

func (*SeriesService) CreateSeries

func (s *SeriesService) CreateSeries(ctx context.Context, series *models.Series) error

CreateSeries creates a new series

func (*SeriesService) DeleteSeries

func (s *SeriesService) DeleteSeries(ctx context.Context, authorID, seriesID string) error

DeleteSeries deletes a series

func (*SeriesService) GetSeries

func (s *SeriesService) GetSeries(ctx context.Context, authorID, seriesID string) (*models.Series, error)

GetSeries retrieves a series by author ID and series ID

func (*SeriesService) ListSeriesByAuthor

func (s *SeriesService) ListSeriesByAuthor(ctx context.Context, authorID string, limit int) ([]*models.Series, error)

ListSeriesByAuthor lists series for an author

func (*SeriesService) RemoveArticleFromSeries

func (s *SeriesService) RemoveArticleFromSeries(ctx context.Context, articleID string) error

RemoveArticleFromSeries removes an article from a series

func (*SeriesService) ReorderArticles

func (s *SeriesService) ReorderArticles(ctx context.Context, seriesID string, articleOrders map[string]int) error

ReorderArticles updates the order of articles in a series

func (*SeriesService) UpdateSeries

func (s *SeriesService) UpdateSeries(ctx context.Context, series *models.Series) error

UpdateSeries updates an existing series

Jump to

Keyboard shortcuts

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