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