93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
package useCases
|
|
|
|
import (
|
|
"RenewCMS/internal/domain/article"
|
|
"RenewCMS/internal/domain/audit"
|
|
"RenewCMS/internal/domain/builtintype"
|
|
"RenewCMS/internal/domain/role"
|
|
"RenewCMS/internal/domain/transaction"
|
|
"context"
|
|
"errors"
|
|
)
|
|
|
|
type DeleteStoryUseCase struct {
|
|
transactor transaction.Transactor
|
|
articles article.Repository
|
|
recorder audit.Recorder
|
|
}
|
|
|
|
type DeleteStoryCommand struct {
|
|
StoryID uint32
|
|
Caller ContentCaller
|
|
}
|
|
|
|
func NewDeleteStoryUseCase(transactor transaction.Transactor, articles article.Repository, recorder audit.Recorder) *DeleteStoryUseCase {
|
|
return &DeleteStoryUseCase{transactor: transactor, articles: articles, recorder: recorder}
|
|
}
|
|
|
|
func (u *DeleteStoryUseCase) DeleteStory(ctx context.Context, command DeleteStoryCommand) error {
|
|
if err := requireDeleteOnEveryVersion(command.Caller); err != nil {
|
|
return err
|
|
}
|
|
var deleted []article.Article
|
|
err := u.transactor.Run(ctx, func(tx transaction.Repositories) error {
|
|
versions, err := lockStoryVersions(tx, command.StoryID, command.Caller)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deleted = sortByCreation(versions)
|
|
return tx.Articles.DeleteStory(command.StoryID)
|
|
})
|
|
if err != nil {
|
|
u.recordRefusal(ctx, command, err)
|
|
return err
|
|
}
|
|
for _, version := range deleted {
|
|
event := articleEvent(audit.ActionArticleDelete, version)
|
|
event.Params[audit.ParamStoryID] = command.StoryID
|
|
u.recorder.Record(ctx, event)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func requireDeleteOnEveryVersion(caller ContentCaller) error {
|
|
if caller.Grants.ScopeFor(role.KeyContentDelete, builtintype.KeyArticle) != role.ScopeAll {
|
|
return role.MissingPermissionError{Key: role.KeyContentDelete}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func lockStoryVersions(tx transaction.Repositories, storyID uint32, caller ContentCaller) ([]article.Article, error) {
|
|
locked := []uint32{}
|
|
for attempt := 0; attempt < maxSiteLockRounds; attempt++ {
|
|
versions, err := tx.Articles.ListByStory(storyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(caller.readableVersions(versions)) == 0 {
|
|
return nil, article.ErrArticleNotFound
|
|
}
|
|
needed := siteIDsOf(versions)
|
|
if allLocked(locked, needed) {
|
|
return versions, nil
|
|
}
|
|
if err := lockSites(tx, needed); err != nil {
|
|
return nil, err
|
|
}
|
|
locked = needed
|
|
}
|
|
return nil, transaction.ErrLockContention
|
|
}
|
|
|
|
func (u *DeleteStoryUseCase) recordRefusal(ctx context.Context, command DeleteStoryCommand, cause error) {
|
|
if isNotFound(cause) || errors.Is(cause, role.ErrMissingPermission) {
|
|
return
|
|
}
|
|
versions, err := u.articles.ListByStory(command.StoryID)
|
|
readable := sortByCreation(command.Caller.readableVersions(versions))
|
|
if err != nil || len(readable) == 0 {
|
|
return
|
|
}
|
|
recordFailure(ctx, u.recorder, articleEvent(audit.ActionArticleDelete, readable[0]), cause)
|
|
}
|