feat: editor write path

This commit is contained in:
Florian Sylvain committed 2026-11-02 03:24:30 +01:00
1 parent d9158025d4
commit a0b3c58c52
88 files changed
+5738 -263

No files matched your search

@@ -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
}