feat: editor write path
This commit is contained in:
1 parent
d9158025d4
commit
a0b3c58c52
88 files changed
+5738
-263
No files matched your search
@@ -6,8 +6,11 @@ import (
|
||||
"RenewCMS/internal/domain/author"
|
||||
"RenewCMS/internal/domain/builtintype"
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/domain/language"
|
||||
"RenewCMS/internal/domain/localization"
|
||||
"RenewCMS/internal/domain/role"
|
||||
"RenewCMS/internal/domain/site"
|
||||
"RenewCMS/internal/domain/transaction"
|
||||
"context"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -21,9 +24,13 @@ type UpdateArticleUseCase struct {
|
||||
sites site.Repository
|
||||
authors author.Repository
|
||||
images image.Repository
|
||||
localization localization.Repository
|
||||
storyMover StoryMover
|
||||
}
|
||||
|
||||
type UpdateArticleCommand struct {
|
||||
Title *string
|
||||
IfRevision *uint32
|
||||
Body *string
|
||||
SiteID *uint32
|
||||
AuthorIDs *[]uint32
|
||||
@@ -42,8 +49,8 @@ type UpdateArticleCommand struct {
|
||||
Format article.BodyFormat
|
||||
}
|
||||
|
||||
func NewUpdateArticleUseCase(articleRepository article.Repository, bodyRenderer article.BodyRenderer, recorder audit.Recorder, sites site.Repository, authors author.Repository, images image.Repository) *UpdateArticleUseCase {
|
||||
return &UpdateArticleUseCase{articleRepository: articleRepository, bodyRenderer: bodyRenderer, recorder: recorder, sites: sites, authors: authors, images: images}
|
||||
func NewUpdateArticleUseCase(articleRepository article.Repository, bodyRenderer article.BodyRenderer, recorder audit.Recorder, sites site.Repository, authors author.Repository, images image.Repository, localizationRepository localization.Repository, storyMover StoryMover) *UpdateArticleUseCase {
|
||||
return &UpdateArticleUseCase{articleRepository: articleRepository, bodyRenderer: bodyRenderer, recorder: recorder, sites: sites, authors: authors, images: images, localization: localizationRepository, storyMover: storyMover}
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) UpdateBody(ctx context.Context, id uint32, caller ContentCaller, body string, format article.BodyFormat) (article.Article, error) {
|
||||
@@ -61,10 +68,29 @@ func (g *UpdateArticleUseCase) Update(ctx context.Context, id uint32, caller Con
|
||||
if err := caller.authorize(role.KeyContentUpdate, builtintype.KeyArticle, before.CreatedBy); err != nil {
|
||||
return article.Article{}, err
|
||||
}
|
||||
if !caller.seesArticle(before) {
|
||||
return article.Article{}, article.ErrArticleNotFound
|
||||
}
|
||||
if command.IfRevision != nil && before.EditRevision != *command.IfRevision {
|
||||
err := &article.RevisionConflictError{Current: before.EditRevision}
|
||||
g.recordFailure(ctx, audit.ActionArticleEdit, before, err)
|
||||
return article.Article{}, err
|
||||
}
|
||||
if command.Title != nil {
|
||||
if err := article.ValidateTitle(*command.Title); err != nil {
|
||||
g.recordFailure(ctx, audit.ActionArticleEdit, before, err)
|
||||
return article.Article{}, err
|
||||
}
|
||||
}
|
||||
destination, err := g.destinationSite(command)
|
||||
if err != nil {
|
||||
return article.Article{}, err
|
||||
}
|
||||
movesWholeStory, err := g.checkSingleVersionMove(before, caller, command)
|
||||
if err != nil {
|
||||
g.recordFailure(ctx, audit.ActionArticleEdit, before, err)
|
||||
return article.Article{}, err
|
||||
}
|
||||
if err := g.ensureAuthorsExist(command.AuthorIDs); err != nil {
|
||||
return article.Article{}, err
|
||||
}
|
||||
@@ -78,7 +104,28 @@ func (g *UpdateArticleUseCase) Update(ctx context.Context, id uint32, caller Con
|
||||
return article.Article{}, err
|
||||
}
|
||||
update.Slug = slug
|
||||
if movesWholeStory {
|
||||
return g.updateWithStory(ctx, id, caller, before, command, destination, update, slug)
|
||||
}
|
||||
updated, err := g.articleRepository.Update(id, update)
|
||||
return g.finishEdit(ctx, before, command, slug, destination, updated, err)
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) updateWithStory(ctx context.Context, id uint32, caller ContentCaller, before article.Article, command UpdateArticleCommand, destination *site.Site, update article.Update, slug *string) (article.Article, error) {
|
||||
var updated article.Article
|
||||
var writeErr error
|
||||
move := MoveStoryCommand{StoryID: before.StoryID, SiteKey: destination.Key, Caller: caller, SkipArticleID: id}
|
||||
_, err := g.storyMover.MoveStoryThen(ctx, move, func(tx transaction.Repositories) error {
|
||||
updated, writeErr = tx.Articles.Update(id, update)
|
||||
return writeErr
|
||||
})
|
||||
if err != nil && writeErr == nil {
|
||||
return article.Article{}, err
|
||||
}
|
||||
return g.finishEdit(ctx, before, command, slug, destination, updated, err)
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) finishEdit(ctx context.Context, before article.Article, command UpdateArticleCommand, slug *string, destination *site.Site, updated article.Article, err error) (article.Article, error) {
|
||||
if isSlugTakenError(err) {
|
||||
err = g.raceConflict(before, command, slug)
|
||||
}
|
||||
@@ -92,6 +139,8 @@ func (g *UpdateArticleUseCase) Update(ctx context.Context, id uint32, caller Con
|
||||
|
||||
func (c UpdateArticleCommand) toUpdate() article.Update {
|
||||
update := article.Update{
|
||||
Title: c.Title,
|
||||
IfRevision: c.IfRevision,
|
||||
Body: c.Body,
|
||||
SiteID: c.SiteID,
|
||||
AuthorIDs: c.AuthorIDs,
|
||||
@@ -188,6 +237,47 @@ func (g *UpdateArticleUseCase) destinationSite(command UpdateArticleCommand) (*s
|
||||
return &destination, nil
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) checkSingleVersionMove(before article.Article, caller ContentCaller, command UpdateArticleCommand) (bool, error) {
|
||||
if command.SiteID == nil || *command.SiteID == before.SiteID {
|
||||
return false, nil
|
||||
}
|
||||
versions, err := g.articleRepository.ListByStory(before.StoryID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(caller.readableVersions(versions)) > 1 {
|
||||
return false, article.ErrStoryMoveRequired
|
||||
}
|
||||
if err := requireUpdateOnAll(caller, versions); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(versions) > 1 {
|
||||
return true, nil
|
||||
}
|
||||
rows, err := g.localization.Rows(*command.SiteID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if missing := localization.MissingTags(rows, []string{before.Lang}); len(missing) > 0 {
|
||||
return false, &article.LanguageNotEnabledError{Missing: missing}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) checkPublishable(before article.Article, isOnline bool) error {
|
||||
if !isOnline || before.IsOnline || before.Lang == language.Unassigned {
|
||||
return nil
|
||||
}
|
||||
rows, err := g.localization.Rows(before.SiteID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !localization.EnabledTag(rows, before.Lang) {
|
||||
return article.ErrLanguageDisabled
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) ensureAuthorsExist(ids *[]uint32) error {
|
||||
if ids == nil {
|
||||
return nil
|
||||
@@ -230,8 +320,7 @@ func (g *UpdateArticleUseCase) recordEdit(ctx context.Context, before article.Ar
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
event := audit.NewEvent(audit.ActionArticleEdit, audit.CategoryArticles)
|
||||
event.Target = articleTarget(after)
|
||||
event := articleEvent(audit.ActionArticleEdit, after)
|
||||
event.Changes = changes
|
||||
g.recorder.Record(ctx, event)
|
||||
}
|
||||
@@ -266,6 +355,10 @@ func (g *UpdateArticleUseCase) UpdateIsOnline(ctx context.Context, id uint32, ca
|
||||
if err := caller.authorize(role.KeyContentPublish, builtintype.KeyArticle, before.CreatedBy); err != nil {
|
||||
return article.Article{}, err
|
||||
}
|
||||
if err := g.checkPublishable(before, isOnline); err != nil {
|
||||
g.recordFailure(ctx, publishAction(isOnline), before, err)
|
||||
return article.Article{}, err
|
||||
}
|
||||
updated, err := g.articleRepository.UpdateIsOnline(id, isOnline)
|
||||
if err != nil {
|
||||
g.recordFailure(ctx, publishAction(isOnline), before, err)
|
||||
@@ -287,14 +380,11 @@ func (g *UpdateArticleUseCase) recordChange(ctx context.Context, action string,
|
||||
if len(changes) == 0 {
|
||||
return
|
||||
}
|
||||
event := audit.NewEvent(action, audit.CategoryArticles)
|
||||
event.Target = articleTarget(after)
|
||||
event := articleEvent(action, after)
|
||||
event.Changes = changes
|
||||
g.recorder.Record(ctx, event)
|
||||
}
|
||||
|
||||
func (g *UpdateArticleUseCase) recordFailure(ctx context.Context, action string, before article.Article, cause error) {
|
||||
event := audit.NewEvent(action, audit.CategoryArticles)
|
||||
event.Target = articleTarget(before)
|
||||
recordFailure(ctx, g.recorder, event, cause)
|
||||
recordFailure(ctx, g.recorder, articleEvent(action, before), cause)
|
||||
}
|
||||
Reference in new issue
Block a user