163 lines
4.5 KiB
Go
163 lines
4.5 KiB
Go
package article
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/author"
|
|
"errors"
|
|
"slices"
|
|
)
|
|
|
|
var (
|
|
ErrRevisionConflict = errors.New("the article changed since it was opened")
|
|
ErrLanguageTaken = errors.New("the story already holds a version in that language")
|
|
ErrLanguageUnavailable = errors.New("the language cannot be used for this story")
|
|
ErrLanguageExists = errors.New("a readable version in that language already exists")
|
|
ErrLanguageNotEnabled = errors.New("the language is not enabled on the site")
|
|
ErrLanguageDisabled = errors.New("the language is disabled on the site")
|
|
ErrSourceUnavailable = errors.New("the source version cannot be used")
|
|
ErrSourceOtherStory = errors.New("the source version belongs to another story")
|
|
ErrInvalidTranslation = errors.New("the translation request is not valid")
|
|
ErrStoryMoveRequired = errors.New("the story must be moved as a whole")
|
|
ErrSlugConflict = errors.New("an address already exists on the destination site")
|
|
ErrTargetSiteDeleted = errors.New("the target site was deleted while the article was being created")
|
|
)
|
|
|
|
const (
|
|
CapabilityRead = "read"
|
|
CapabilityEdit = "edit"
|
|
CapabilityPublish = "publish"
|
|
CapabilityDelete = "delete"
|
|
CapabilityCreateTranslation = "create_translation"
|
|
)
|
|
|
|
type RevisionConflictError struct {
|
|
Current uint32
|
|
}
|
|
|
|
func (e *RevisionConflictError) Error() string {
|
|
return ErrRevisionConflict.Error()
|
|
}
|
|
|
|
func (e *RevisionConflictError) Is(target error) bool {
|
|
return target == ErrRevisionConflict
|
|
}
|
|
|
|
type LanguageExistsError struct {
|
|
ArticleID uint32
|
|
}
|
|
|
|
func (e *LanguageExistsError) Error() string {
|
|
return ErrLanguageExists.Error()
|
|
}
|
|
|
|
func (e *LanguageExistsError) Is(target error) bool {
|
|
return target == ErrLanguageExists
|
|
}
|
|
|
|
type LanguageNotEnabledError struct {
|
|
Missing []string
|
|
}
|
|
|
|
func (e *LanguageNotEnabledError) Error() string {
|
|
return ErrLanguageNotEnabled.Error()
|
|
}
|
|
|
|
func (e *LanguageNotEnabledError) Is(target error) bool {
|
|
return target == ErrLanguageNotEnabled
|
|
}
|
|
|
|
type SlugConflictError struct {
|
|
ArticleIDs []uint32
|
|
}
|
|
|
|
func (e *SlugConflictError) Error() string {
|
|
return ErrSlugConflict.Error()
|
|
}
|
|
|
|
func (e *SlugConflictError) Is(target error) bool {
|
|
return target == ErrSlugConflict
|
|
}
|
|
|
|
type Translation struct {
|
|
ID uint32 `json:"id"`
|
|
Lang string `json:"lang"`
|
|
Title string `json:"title"`
|
|
IsOnline bool `json:"is_online"`
|
|
LanguageEnabled bool `json:"language_enabled"`
|
|
Capabilities []string `json:"capabilities"`
|
|
}
|
|
|
|
type Source struct {
|
|
ArticleID uint32 `json:"article_id"`
|
|
Revision uint32 `json:"revision"`
|
|
}
|
|
|
|
type Impact struct {
|
|
Edited bool
|
|
Content bool
|
|
}
|
|
|
|
func ImpactOf(before Article, after Article, authorsChanged bool) Impact {
|
|
content := before.Title != after.Title ||
|
|
before.Body != after.Body ||
|
|
before.Excerpt != after.Excerpt ||
|
|
before.Slug != after.Slug ||
|
|
before.CoverImageAlt != after.CoverImageAlt ||
|
|
before.SEOTitle != after.SEOTitle ||
|
|
before.SEODescription != after.SEODescription ||
|
|
before.SocialTitle != after.SocialTitle ||
|
|
before.SocialDescription != after.SocialDescription
|
|
edited := content ||
|
|
authorsChanged ||
|
|
before.SiteID != after.SiteID ||
|
|
before.CanonicalURL != after.CanonicalURL ||
|
|
before.Noindex != after.Noindex ||
|
|
!sameImageID(before.CoverImageID, after.CoverImageID) ||
|
|
!sameImageID(before.SocialImageID, after.SocialImageID) ||
|
|
!slices.Equal(before.SEODismissed, after.SEODismissed)
|
|
return Impact{Edited: edited, Content: content}
|
|
}
|
|
|
|
const InitialRevision uint32 = 1
|
|
|
|
func (a Article) Opened() Article {
|
|
a.EditRevision = InitialRevision
|
|
a.ContentRevision = InitialRevision
|
|
return a
|
|
}
|
|
|
|
func (a Article) Revised(impact Impact) Article {
|
|
if impact.Edited {
|
|
a.EditRevision++
|
|
}
|
|
if impact.Content {
|
|
a.ContentRevision++
|
|
}
|
|
return a
|
|
}
|
|
|
|
func sameImageID(left *uint32, right *uint32) bool {
|
|
if left == nil || right == nil {
|
|
return left == right
|
|
}
|
|
return *left == *right
|
|
}
|
|
|
|
func (a Article) CopiedInto(draft Article) Article {
|
|
draft.Body = a.Body
|
|
draft.Excerpt = a.Excerpt
|
|
draft.SEOTitle = a.SEOTitle
|
|
draft.SEODescription = a.SEODescription
|
|
draft.SocialTitle = a.SocialTitle
|
|
draft.SocialDescription = a.SocialDescription
|
|
draft.CoverImageID = a.CoverImageID
|
|
draft.CoverImageAlt = a.CoverImageAlt
|
|
draft.SocialImageID = a.SocialImageID
|
|
draft.Authors = slices.Clone(a.Authors)
|
|
if draft.Authors == nil {
|
|
draft.Authors = []author.Byline{}
|
|
}
|
|
draft.SourceArticleID = &a.ID
|
|
draft.SourceRevision = a.ContentRevision
|
|
return draft
|
|
}
|