refac: article reads, story delete, no leaks, stored SEO status, rate limit, cleanup (%site%, article repo split, old lang repos, one default per site rule to domain)
This commit is contained in:
1 parent
9d284e65a9
commit
7c15206170
85 files changed
+1788
-593
No files matched your search
@@ -0,0 +1,92 @@
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user