131 lines
4.0 KiB
Go
131 lines
4.0 KiB
Go
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
|
|
}
|