Files
RenewCMS/internal/infrastructure/useCases/seoRules.go
T

98 lines
4.0 KiB
Go

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}
}