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,97 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/site"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func metaTitle(art article.Article) (string, string) {
|
||||
if art.SEOTitle != "" {
|
||||
return art.SEOTitle, article.SEOSourceSEOTitle
|
||||
}
|
||||
return art.Title, article.SEOSourceTitle
|
||||
}
|
||||
|
||||
func resolveDescription(art article.Article, owner site.Site, facts bodyFacts) (string, string) {
|
||||
if art.SEODescription != "" {
|
||||
return art.SEODescription, article.SEOSourceSEODescription
|
||||
}
|
||||
if art.Excerpt != "" {
|
||||
return art.Excerpt, article.SEOSourceExcerpt
|
||||
}
|
||||
if sentence := firstSentence(facts.spacedText); sentence != "" {
|
||||
return sentence, article.SEOSourceBody
|
||||
}
|
||||
if owner.DefaultDescription != "" {
|
||||
return owner.DefaultDescription, article.SEOSourceSite
|
||||
}
|
||||
return "", article.SEOSourceNone
|
||||
}
|
||||
|
||||
func suggestion(code string, field string, params map[string]int) article.SEOSuggestion {
|
||||
return article.SEOSuggestion{Code: code, Field: field, Params: params}
|
||||
}
|
||||
|
||||
func rawSuggestions(art article.Article, owner site.Site, facts bodyFacts) []article.SEOSuggestion {
|
||||
suggestions := []article.SEOSuggestion{}
|
||||
title, _ := metaTitle(art)
|
||||
titleRunes := utf8.RuneCountInString(title)
|
||||
if titleRunes > article.SEOTitleMaxRunes {
|
||||
suggestions = append(suggestions, suggestion(article.DismissTitleLong, article.SEOFieldTitle, map[string]int{"length": titleRunes, "max": article.SEOTitleMaxRunes}))
|
||||
}
|
||||
if strings.TrimSpace(title) == "" {
|
||||
suggestions = append(suggestions, suggestion(article.DismissTitleEmpty, article.SEOFieldTitle, map[string]int{}))
|
||||
}
|
||||
description, _ := resolveDescription(art, owner, facts)
|
||||
descriptionRunes := utf8.RuneCountInString(description)
|
||||
if descriptionRunes < article.SEODescriptionMinRunes {
|
||||
suggestions = append(suggestions, suggestion(article.DismissDescShort, article.SEOFieldDescription, map[string]int{"length": descriptionRunes, "min": article.SEODescriptionMinRunes}))
|
||||
}
|
||||
if descriptionRunes > article.SEODescriptionMaxRunes {
|
||||
suggestions = append(suggestions, suggestion(article.DismissDescLong, article.SEOFieldDescription, map[string]int{"length": descriptionRunes, "max": article.SEODescriptionMaxRunes}))
|
||||
}
|
||||
if art.CoverImageID != nil && strings.TrimSpace(art.CoverImageAlt) == "" {
|
||||
suggestions = append(suggestions, suggestion(article.DismissCoverAlt, article.SEOFieldCoverAlt, map[string]int{}))
|
||||
}
|
||||
if art.CoverImageID == nil {
|
||||
suggestions = append(suggestions, suggestion(article.DismissCoverNone, article.SEOFieldCoverID, map[string]int{}))
|
||||
}
|
||||
slugRunes := utf8.RuneCountInString(art.Slug)
|
||||
if slugRunes > article.SEOSlugMaxRunes {
|
||||
suggestions = append(suggestions, suggestion(article.DismissSlugLong, article.SEOFieldSlug, map[string]int{"length": slugRunes, "max": article.SEOSlugMaxRunes}))
|
||||
}
|
||||
if len(art.Authors) == 0 {
|
||||
suggestions = append(suggestions, suggestion(article.DismissNoAuthor, article.SEOFieldAuthors, map[string]int{}))
|
||||
}
|
||||
if facts.textRunes >= article.SEOHeadingMinTextRunes && !facts.hasSubheading {
|
||||
suggestions = append(suggestions, suggestion(article.DismissNoHeading, article.SEOFieldBody, map[string]int{}))
|
||||
}
|
||||
if facts.imagesWithoutAlt > 0 {
|
||||
suggestions = append(suggestions, suggestion(article.DismissBodyAlt, article.SEOFieldBody, map[string]int{"count": facts.imagesWithoutAlt}))
|
||||
}
|
||||
if facts.textRunes < article.SEOShortBodyRunes {
|
||||
suggestions = append(suggestions, suggestion(article.DismissShort, article.SEOFieldBody, map[string]int{"length": facts.textRunes, "min": article.SEOShortBodyRunes}))
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
|
||||
func assessSEO(art article.Article, owner site.Site) article.SEOAssessment {
|
||||
facts := analyzeBody(art.Body)
|
||||
kept := []article.SEOSuggestion{}
|
||||
for _, found := range rawSuggestions(art, owner, facts) {
|
||||
if !slices.Contains(art.SEODismissed, found.Code) {
|
||||
kept = append(kept, found)
|
||||
}
|
||||
}
|
||||
status := article.SEOStatusReady
|
||||
if len(kept) > 0 {
|
||||
status = article.SEOStatusSuggestions
|
||||
}
|
||||
if art.Noindex {
|
||||
status = article.SEOStatusHidden
|
||||
}
|
||||
return article.SEOAssessment{Status: status, Suggestions: kept}
|
||||
}
|
||||
Reference in new issue
Block a user