feat: SEO backend output (2/4)
This commit is contained in:
1 parent
0eb46b0d74
commit
eb9876fde3
32 files changed
+3859
-65
No files matched your search
@@ -0,0 +1,130 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/domain/site"
|
||||
)
|
||||
|
||||
type CheckArticleSEOUseCase struct {
|
||||
articleRepository article.Repository
|
||||
sites site.Repository
|
||||
images image.Repository
|
||||
}
|
||||
|
||||
type CheckArticleSEOCommand struct {
|
||||
Body *string
|
||||
Slug *string
|
||||
Excerpt *string
|
||||
SEOTitle *string
|
||||
SEODescription *string
|
||||
CoverImageID article.Nullable[uint32]
|
||||
CoverImageAlt *string
|
||||
SocialTitle *string
|
||||
SocialDescription *string
|
||||
SocialImageID article.Nullable[uint32]
|
||||
CanonicalURL *string
|
||||
Noindex *bool
|
||||
SEODismissed *[]string
|
||||
}
|
||||
|
||||
func NewCheckArticleSEOUseCase(articleRepository article.Repository, sites site.Repository, images image.Repository) *CheckArticleSEOUseCase {
|
||||
return &CheckArticleSEOUseCase{articleRepository: articleRepository, sites: sites, images: images}
|
||||
}
|
||||
|
||||
func (u *CheckArticleSEOUseCase) Check(id uint32, command CheckArticleSEOCommand) (article.SEOCheck, error) {
|
||||
saved, err := u.articleRepository.Get(id)
|
||||
if err != nil {
|
||||
return article.SEOCheck{}, err
|
||||
}
|
||||
if err := u.validate(command); err != nil {
|
||||
return article.SEOCheck{}, err
|
||||
}
|
||||
owner, err := u.sites.Get(saved.SiteID)
|
||||
if err != nil {
|
||||
return article.SEOCheck{}, err
|
||||
}
|
||||
candidate := command.applyTo(saved)
|
||||
slugCheck, err := u.checkSlug(candidate)
|
||||
if err != nil {
|
||||
return article.SEOCheck{}, err
|
||||
}
|
||||
assessment := assessSEO(candidate, owner)
|
||||
return article.SEOCheck{Status: assessment.Status, Suggestions: assessment.Suggestions, Slug: slugCheck}, nil
|
||||
}
|
||||
|
||||
func (u *CheckArticleSEOUseCase) validate(command CheckArticleSEOCommand) error {
|
||||
if command.Slug != nil && *command.Slug != "" && !article.IsValidSlug(*command.Slug) {
|
||||
return article.ErrInvalidSlug
|
||||
}
|
||||
if command.CanonicalURL != nil {
|
||||
if err := article.ValidateCanonicalURL(*command.CanonicalURL); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if command.CoverImageID.Set {
|
||||
if err := ensureImageExists(u.images, command.CoverImageID.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if command.SocialImageID.Set {
|
||||
return ensureImageExists(u.images, command.SocialImageID.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func overrideText(current string, override *string) string {
|
||||
if override == nil {
|
||||
return current
|
||||
}
|
||||
return *override
|
||||
}
|
||||
|
||||
func overrideImage(current *uint32, override article.Nullable[uint32]) *uint32 {
|
||||
if override.Set {
|
||||
return override.Value
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func (c CheckArticleSEOCommand) applyTo(saved article.Article) article.Article {
|
||||
saved.Body = overrideText(saved.Body, c.Body)
|
||||
saved.Excerpt = overrideText(saved.Excerpt, c.Excerpt)
|
||||
saved.SEOTitle = overrideText(saved.SEOTitle, c.SEOTitle)
|
||||
saved.SEODescription = overrideText(saved.SEODescription, c.SEODescription)
|
||||
saved.CoverImageID = overrideImage(saved.CoverImageID, c.CoverImageID)
|
||||
saved.CoverImageAlt = overrideText(saved.CoverImageAlt, c.CoverImageAlt)
|
||||
saved.SocialTitle = overrideText(saved.SocialTitle, c.SocialTitle)
|
||||
saved.SocialDescription = overrideText(saved.SocialDescription, c.SocialDescription)
|
||||
saved.SocialImageID = overrideImage(saved.SocialImageID, c.SocialImageID)
|
||||
saved.CanonicalURL = overrideText(saved.CanonicalURL, c.CanonicalURL)
|
||||
if c.Noindex != nil {
|
||||
saved.Noindex = *c.Noindex
|
||||
}
|
||||
if c.SEODismissed != nil {
|
||||
saved.SEODismissed = article.NormalizeDismissed(*c.SEODismissed)
|
||||
}
|
||||
if c.Slug != nil {
|
||||
saved.Slug = *c.Slug
|
||||
if saved.Slug == "" {
|
||||
saved.Slug = article.GenerateSlug(saved.Title)
|
||||
}
|
||||
}
|
||||
return saved
|
||||
}
|
||||
|
||||
func (u *CheckArticleSEOUseCase) checkSlug(candidate article.Article) (article.SEOSlugCheck, error) {
|
||||
available := article.SEOSlugCheck{Available: true}
|
||||
if candidate.Slug == "" {
|
||||
return available, nil
|
||||
}
|
||||
taken, err := u.articleRepository.SlugTaken(candidate.SiteID, candidate.Slug, candidate.ID)
|
||||
if err != nil || !taken {
|
||||
return available, err
|
||||
}
|
||||
suggestion, err := freeSlug(u.articleRepository, candidate.SiteID, candidate.Slug, candidate.ID, firstSuggestedAttempt)
|
||||
if err != nil {
|
||||
return article.SEOSlugCheck{}, err
|
||||
}
|
||||
return article.SEOSlugCheck{Suggestion: &suggestion}, nil
|
||||
}
|
||||
Reference in new issue
Block a user