feat: public API, SEO and existing surfaces
This commit is contained in:
1 parent
45fbbc33b1
commit
c390fcfe8d
103 files changed
+4476
-231
No files matched your search
@@ -3,6 +3,7 @@ package useCases
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/domain/localization"
|
||||
"RenewCMS/internal/domain/site"
|
||||
"errors"
|
||||
"strings"
|
||||
@@ -26,10 +27,53 @@ type ResolveArticleSEOUseCase struct {
|
||||
articleRepository article.Repository
|
||||
sites site.Repository
|
||||
images image.Repository
|
||||
localization localization.Repository
|
||||
}
|
||||
|
||||
func NewResolveArticleSEOUseCase(articleRepository article.Repository, sites site.Repository, images image.Repository) *ResolveArticleSEOUseCase {
|
||||
return &ResolveArticleSEOUseCase{articleRepository: articleRepository, sites: sites, images: images}
|
||||
func NewResolveArticleSEOUseCase(articleRepository article.Repository, sites site.Repository, images image.Repository, localizationRepository localization.Repository) *ResolveArticleSEOUseCase {
|
||||
return &ResolveArticleSEOUseCase{articleRepository: articleRepository, sites: sites, images: images, localization: localizationRepository}
|
||||
}
|
||||
|
||||
type seoLanguage struct {
|
||||
defaultTag string
|
||||
locale string
|
||||
alternates []article.Alternate
|
||||
localeAlternates []string
|
||||
}
|
||||
|
||||
func (u *ResolveArticleSEOUseCase) languageOf(art article.Article, owner site.Site) (seoLanguage, error) {
|
||||
if art.Lang == "" {
|
||||
locale := ""
|
||||
if owner.Locale != nil {
|
||||
locale = *owner.Locale
|
||||
}
|
||||
return seoLanguage{alternates: []article.Alternate{}, localeAlternates: []string{}, locale: locale}, nil
|
||||
}
|
||||
rows, err := u.localization.Rows(art.SiteID)
|
||||
if err != nil {
|
||||
return seoLanguage{}, err
|
||||
}
|
||||
versions, err := storyVersions(u.articleRepository, art)
|
||||
if err != nil {
|
||||
return seoLanguage{}, err
|
||||
}
|
||||
siblings := eligibleSiblings(rows, versions, art)
|
||||
localeAlternates := []string{}
|
||||
for _, sibling := range siblings {
|
||||
if resolved := socialLocaleOf(rows, sibling.Lang, owner.Locale); resolved != "" {
|
||||
localeAlternates = append(localeAlternates, resolved)
|
||||
}
|
||||
}
|
||||
locale := socialLocaleOf(rows, art.Lang, owner.Locale)
|
||||
if locale == "" {
|
||||
localeAlternates = []string{}
|
||||
}
|
||||
return seoLanguage{
|
||||
defaultTag: confirmedDefault(rows),
|
||||
locale: locale,
|
||||
alternates: alternatesOf(owner, siblings),
|
||||
localeAlternates: localeAlternates,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *ResolveArticleSEOUseCase) Resolve(id uint32, caller ContentCaller) (article.ResolvedSEO, error) {
|
||||
@@ -48,7 +92,11 @@ func (u *ResolveArticleSEOUseCase) Resolve(id uint32, caller ContentCaller) (art
|
||||
if err != nil {
|
||||
return article.ResolvedSEO{}, err
|
||||
}
|
||||
return buildResolvedSEO(art, owner, picked), nil
|
||||
spoken, err := u.languageOf(art, owner)
|
||||
if err != nil {
|
||||
return article.ResolvedSEO{}, err
|
||||
}
|
||||
return buildResolvedSEO(art, owner, picked, spoken), nil
|
||||
}
|
||||
|
||||
func (u *ResolveArticleSEOUseCase) resolveImage(art article.Article, owner site.Site) (article.SEOImage, error) {
|
||||
@@ -96,11 +144,11 @@ func resolveCanonical(art article.Article, owner site.Site) (string, string) {
|
||||
if art.CanonicalURL != "" {
|
||||
return art.CanonicalURL, article.SEOSourceCanonicalURL
|
||||
}
|
||||
return articleURL(owner, art.Slug), article.SEOSourceSite
|
||||
}
|
||||
|
||||
func articleURL(owner site.Site, slug string) string {
|
||||
return owner.BaseURL + "/" + slug
|
||||
url, patterned := owner.ArticleURL(art.Lang, art.Slug)
|
||||
if patterned {
|
||||
return url, article.SEOSourcePattern
|
||||
}
|
||||
return url, article.SEOSourceSite
|
||||
}
|
||||
|
||||
func resolveAuthors(art article.Article) []article.SEOAuthor {
|
||||
@@ -144,10 +192,12 @@ func buildJSONLD(headline string, description string, picked article.SEOImage, a
|
||||
return ld
|
||||
}
|
||||
|
||||
func buildResolvedSEO(art article.Article, owner site.Site, picked article.SEOImage) article.ResolvedSEO {
|
||||
func buildResolvedSEO(art article.Article, owner site.Site, picked article.SEOImage, spoken seoLanguage) article.ResolvedSEO {
|
||||
meta, titleSource := metaTitle(art)
|
||||
title := applyTitleTemplate(owner.TitleTemplate, meta, owner.SiteName)
|
||||
description, descriptionSource := resolveDescription(art, owner, analyzeBody(art.Body))
|
||||
siteTextAllowed := art.Lang == "" || art.Lang == spoken.defaultTag
|
||||
description, descriptionSource := resolveDescription(art, owner, analyzeBody(art.Body), siteTextAllowed)
|
||||
siteTextSkipped := descriptionSource == article.SEOSourceNone && owner.DefaultDescription != ""
|
||||
canonical, canonicalSource := resolveCanonical(art, owner)
|
||||
authors := resolveAuthors(art)
|
||||
shareTitle := firstNonEmpty(art.SocialTitle, title)
|
||||
@@ -156,10 +206,6 @@ func buildResolvedSEO(art article.Article, owner site.Site, picked article.SEOIm
|
||||
if picked.URL != "" {
|
||||
card = twitterCardLarge
|
||||
}
|
||||
locale := ""
|
||||
if owner.Locale != nil {
|
||||
locale = *owner.Locale
|
||||
}
|
||||
var published *string
|
||||
if art.IsOnline {
|
||||
formatted := formatSEOTime(art.CreatedAt)
|
||||
@@ -172,14 +218,16 @@ func buildResolvedSEO(art article.Article, owner site.Site, picked article.SEOIm
|
||||
Robots: resolveRobots(art, owner),
|
||||
OG: article.SEOOpenGraph{
|
||||
Type: openGraphArticleType, Title: shareTitle, Description: shareDescription, Image: picked.URL, ImageAlt: picked.Alt,
|
||||
URL: canonical, SiteName: owner.SiteName, Locale: locale,
|
||||
URL: canonical, SiteName: owner.SiteName, Locale: spoken.locale, LocaleAlternates: spoken.localeAlternates,
|
||||
},
|
||||
Twitter: article.SEOTwitter{Card: card, Title: shareTitle, Description: shareDescription, Image: picked.URL, ImageAlt: picked.Alt},
|
||||
Image: picked,
|
||||
Authors: authors,
|
||||
PublishedAt: published,
|
||||
ModifiedAt: formatSEOTime(art.UpdatedAt),
|
||||
Fallbacks: article.SEOFallbacks{Title: titleSource, Description: descriptionSource, Image: picked.Source, Canonical: canonicalSource},
|
||||
Fallbacks: article.SEOFallbacks{Title: titleSource, Description: descriptionSource, Image: picked.Source, Canonical: canonicalSource, SiteTextSkipped: siteTextSkipped},
|
||||
JSONLD: buildJSONLD(meta, description, picked, authors, art, canonical),
|
||||
Lang: art.Lang,
|
||||
Alternates: spoken.alternates,
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user