Documentation
¶
Overview ¶
Package cms provides services for Content Management System functionality
Index ¶
- Constants
- Variables
- func DraftReviewContentHash(d *models.Draft) string
- func DraftReviewContentHashWithMedia(d *models.Draft, mediaDigests map[string]string) string
- func RenderDraftPreview(draft *models.Draft) (cmsrender.RenderedArticleContent, error)
- type ArticleService
- func (s *ArticleService) CreateArticle(ctx context.Context, article *models.Article) error
- func (s *ArticleService) DeleteArticle(ctx context.Context, article *models.Article) error
- func (s *ArticleService) GetArticle(ctx context.Context, articleID string) (*models.Article, error)
- func (s *ArticleService) GetArticleBySlug(ctx context.Context, slug string) (*models.Article, error)
- func (s *ArticleService) GetArticleByTenantSlug(ctx context.Context, tenant string, slug string) (*models.Article, error)
- func (s *ArticleService) UpdateArticle(ctx context.Context, article *models.Article) error
- type CategoryService
- func (s *CategoryService) CreateCategory(ctx context.Context, category *models.Category) error
- func (s *CategoryService) DeleteCategory(ctx context.Context, id string) error
- func (s *CategoryService) GetCategory(ctx context.Context, id string) (*models.Category, error)
- func (s *CategoryService) ListCategories(ctx context.Context, parentID *string, limit int) ([]*models.Category, error)
- func (s *CategoryService) UpdateCategory(ctx context.Context, category *models.Category) error
- type DraftEditorialMediaBinding
- type DraftReviewReadState
- type DraftService
- func (s *DraftService) ActiveDraftReviewGrant(ctx context.Context, owner, draftID, reviewer string) (*models.DraftReviewGrant, error)
- func (s *DraftService) ActivePromoPackageReviewGrant(ctx context.Context, owner, packageID, reviewer string) (*models.PromoReviewGrant, error)
- func (s *DraftService) Autosave(ctx context.Context, authorID string, draft *models.Draft) error
- func (s *DraftService) BoundEditorialMediaForCaller(ctx context.Context, caller, draftID, mediaID string) (*models.Media, error)
- func (s *DraftService) CancelScheduledDraft(ctx context.Context, authorID, draftID string) error
- func (s *DraftService) ComposePromoPackage(ctx context.Context, owner string, input PromoPackageComposeInput) (*models.PromoPackage, error)
- func (s *DraftService) CountSharedDraftReviews(ctx context.Context, reviewer string) (int, error)
- func (s *DraftService) CreateDraft(ctx context.Context, draft *models.Draft) error
- func (s *DraftService) DeleteDraft(ctx context.Context, authorID, draftID string) error
- func (s *DraftService) DraftEditorialMediaForCaller(ctx context.Context, caller, draftID string) (*models.Draft, []DraftEditorialMediaBinding, error)
- func (s *DraftService) DraftReviewForCaller(ctx context.Context, caller, draftID string) (*models.Draft, *models.DraftReviewGrant, error)
- func (s *DraftService) DraftReviewState(ctx context.Context, owner, draftID string, draft *models.Draft) (*DraftReviewReadState, error)
- func (s *DraftService) DraftReviewVerdicts(ctx context.Context, owner, draftID string) ([]*models.DraftReviewVerdict, error)
- func (s *DraftService) GetDraft(ctx context.Context, authorID, draftID string) (*models.Draft, error)
- func (s *DraftService) GetPromoPackage(ctx context.Context, owner, packageID string) (*models.PromoPackage, error)
- func (s *DraftService) HasActiveApproval(ctx context.Context, owner, draftID string) (bool, error)
- func (s *DraftService) HasPrincipalApproval(ctx context.Context, owner, draftID string) (bool, error)
- func (s *DraftService) HasUnanimousActiveApproval(ctx context.Context, owner, draftID string) (bool, error)
- func (s *DraftService) ListPromoPackages(ctx context.Context, owner string, limit int, cursor string) ([]*models.PromoPackage, string, error)
- func (s *DraftService) OwnedDraftReviews(ctx context.Context, owner string) ([]*models.DraftReviewGrant, error)
- func (s *DraftService) OwnedPromoPackageReviews(ctx context.Context, owner string) ([]*models.PromoReviewGrant, error)
- func (s *DraftService) PreviewDraft(ctx context.Context, authorID, draftID string) (cmsrender.RenderedArticleContent, error)
- func (s *DraftService) PromoPackageForCaller(ctx context.Context, caller, packageID string) (*models.PromoPackage, *models.PromoReviewGrant, error)
- func (s *DraftService) PromoPackageReviewState(ctx context.Context, owner, packageID string, pkg *models.PromoPackage) (*PromoPackageReviewReadState, error)
- func (s *DraftService) PromoPackageVerdicts(ctx context.Context, owner, packageID string) ([]*models.PromoReviewVerdict, error)
- func (s *DraftService) PublishDraft(ctx context.Context, authorID, draftID string) (*models.Article, error)
- func (s *DraftService) PublishDraftWithAttribution(ctx context.Context, authorID, draftID, actedBy string) (*models.Article, error)
- func (s *DraftService) ReleasePromoPackage(ctx context.Context, owner, packageID string) (*PromoPackageRelease, error)
- func (s *DraftService) RevokeDraftReview(ctx context.Context, owner, draftID, reviewer string) error
- func (s *DraftService) RevokePromoPackageReview(ctx context.Context, owner, packageID, reviewer string) error
- func (s *DraftService) ScheduleDraft(ctx context.Context, authorID, draftID string, scheduledAt time.Time) error
- func (s *DraftService) SetEditorialMedia(ctx context.Context, authorID, draftID string, usages []models.DraftMediaUsage) (*models.Draft, error)
- func (s *DraftService) SetEditorialMediaRepository(repo editorialMediaRepository)
- func (s *DraftService) SetEditorialPublishMinter(minter editorialPublishMinter)
- func (s *DraftService) SetPrincipalUsernameProvider(provider func(context.Context) (string, error))
- func (s *DraftService) SetPromoPackageRepository(repo promoPackageRepository)
- func (s *DraftService) SetPromoStatusCreator(creator promoStatusCreator)
- func (s *DraftService) ShareDraftForReview(ctx context.Context, owner, draftID, reviewer string) (*models.DraftReviewGrant, error)
- func (s *DraftService) SharePromoPackageForReview(ctx context.Context, owner, packageID, reviewer string) (*models.PromoReviewGrant, error)
- func (s *DraftService) SharedDraftReviews(ctx context.Context, reviewer string, limit int, cursor string) ([]*models.DraftReviewGrant, string, error)
- func (s *DraftService) SharedPromoPackageReviews(ctx context.Context, reviewer string, limit int, cursor string) ([]*models.PromoReviewGrant, string, error)
- func (s *DraftService) SubmitDraftReview(ctx context.Context, caller, owner, draftID, verdict, notes string, ...) (*models.DraftReviewVerdict, error)
- func (s *DraftService) SubmitPromoPackageReview(ctx context.Context, ...) (*models.PromoReviewVerdict, error)
- func (s *DraftService) UpdateDraft(ctx context.Context, authorID string, draft *models.Draft) error
- type EditorialPublishedMedia
- type FederationService
- type LegacyArticleMigrationCandidate
- type LegacyArticleMigrationConflict
- type LegacyArticleMigrationPlan
- type LegacyArticleMigrationSkipped
- type PromoPackageComposeInput
- type PromoPackageRelease
- type PromoPackageResolvedAsset
- type PromoPackageReviewReadState
- type PromoPackageStampError
- type PublicationService
- func (s *PublicationService) AddMember(ctx context.Context, member *models.PublicationMember) error
- func (s *PublicationService) CreatePublication(ctx context.Context, publication *models.Publication) error
- func (s *PublicationService) DeletePublication(ctx context.Context, id string) error
- func (s *PublicationService) GetMember(ctx context.Context, publicationID, userID string) (*models.PublicationMember, error)
- func (s *PublicationService) GetPublication(ctx context.Context, id string) (*models.Publication, error)
- func (s *PublicationService) ListMembers(ctx context.Context, publicationID string) ([]*models.PublicationMember, error)
- func (s *PublicationService) RemoveMember(ctx context.Context, publicationID, userID string) error
- func (s *PublicationService) UpdateMemberRole(ctx context.Context, publicationID, userID, role string) error
- func (s *PublicationService) UpdatePublication(ctx context.Context, publication *models.Publication) error
- type RevisionService
- func (s *RevisionService) CreateRevision(ctx context.Context, article *models.Article) (*models.Revision, error)
- func (s *RevisionService) GetRevision(ctx context.Context, objectID string, version int) (*models.Revision, error)
- func (s *RevisionService) ListRevisions(ctx context.Context, objectID string, limit int) ([]*models.Revision, error)
- func (s *RevisionService) RestoreRevision(ctx context.Context, objectID string, version int) (*models.Article, error)
- type SeriesService
- func (s *SeriesService) AddArticleToSeries(ctx context.Context, articleID string, seriesID string, order int) error
- func (s *SeriesService) CreateSeries(ctx context.Context, series *models.Series) error
- func (s *SeriesService) DeleteSeries(ctx context.Context, authorID, seriesID string) error
- func (s *SeriesService) GetSeries(ctx context.Context, authorID, seriesID string) (*models.Series, error)
- func (s *SeriesService) ListSeriesByAuthor(ctx context.Context, authorID string, limit int) ([]*models.Series, error)
- func (s *SeriesService) RemoveArticleFromSeries(ctx context.Context, articleID string) error
- func (s *SeriesService) ReorderArticles(ctx context.Context, seriesID string, articleOrders map[string]int) error
- func (s *SeriesService) UpdateSeries(ctx context.Context, series *models.Series) error
Constants ¶
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" )
Draft review verdict values.
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.
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.
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 ¶
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 = errors.New("instance principal is unavailable") // ErrInstancePrincipalNotConfigured means the provider returned no principal username. ErrInstancePrincipalNotConfigured = errors.New("instance principal is not configured") )
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") // 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
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
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 ¶
CreateArticle creates a new article
func (*ArticleService) DeleteArticle ¶
DeleteArticle deletes an article and federates a Delete activity best-effort.
func (*ArticleService) GetArticle ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
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 ¶
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
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 ¶
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
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
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
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 ¶
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 ¶
UpdateSeries updates an existing series