feat: editor write path
This commit is contained in:
1 parent
d9158025d4
commit
a0b3c58c52
88 files changed
+5738
-263
No files matched your search
@@ -137,6 +137,9 @@ type Article struct {
|
||||
Lang string `json:"lang"`
|
||||
StoryID uint32 `json:"story_id"`
|
||||
ContentRevision uint32 `json:"-"`
|
||||
EditRevision uint32 `json:"-"`
|
||||
SourceArticleID *uint32 `json:"-"`
|
||||
SourceRevision uint32 `json:"-"`
|
||||
}
|
||||
|
||||
func (a Article) UsesImage(imageID uint32) bool {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
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")
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
@@ -47,6 +47,8 @@ type Nullable[T any] struct {
|
||||
}
|
||||
|
||||
type Update struct {
|
||||
Title *string
|
||||
IfRevision *uint32
|
||||
Body *string
|
||||
SiteID *uint32
|
||||
AuthorIDs *[]uint32
|
||||
@@ -107,4 +109,6 @@ type Repository interface {
|
||||
RestoreImage(references ImageReferences, imageID uint32) error
|
||||
RepointImage(fromImageIDs []uint32, toImageID uint32) ([]uint32, error)
|
||||
AssignUnassignedLanguage(siteID uint32, lang string) (int, error)
|
||||
ListByStory(storyID uint32) ([]Article, error)
|
||||
MoveToSite(ids []uint32, siteID uint32) error
|
||||
}
|
||||
Reference in new issue
Block a user