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
@@ -0,0 +1,152 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/language"
|
||||
"RenewCMS/internal/domain/localization"
|
||||
"RenewCMS/internal/domain/site"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ResolvePublicArticleUseCase struct {
|
||||
articles article.Repository
|
||||
sites site.Repository
|
||||
localization localization.Repository
|
||||
bodyRenderer article.BodyRenderer
|
||||
}
|
||||
|
||||
func NewResolvePublicArticleUseCase(articles article.Repository, sites site.Repository, localizationRepository localization.Repository, bodyRenderer article.BodyRenderer) *ResolvePublicArticleUseCase {
|
||||
return &ResolvePublicArticleUseCase{articles: articles, sites: sites, localization: localizationRepository, bodyRenderer: bodyRenderer}
|
||||
}
|
||||
|
||||
type PublicArticle struct {
|
||||
Article article.Article
|
||||
Viewer ContentCaller
|
||||
Context ArticleContext
|
||||
Alternates []article.Alternate
|
||||
RequestedLang string
|
||||
Fallback bool
|
||||
}
|
||||
|
||||
type PublicStoryQuery struct {
|
||||
SiteKey string
|
||||
StoryID uint32
|
||||
Lang *string
|
||||
FallbackDefault bool
|
||||
Format article.BodyFormat
|
||||
}
|
||||
|
||||
type PublicLookupQuery struct {
|
||||
SiteKey string
|
||||
Lang string
|
||||
Slug string
|
||||
Format article.BodyFormat
|
||||
}
|
||||
|
||||
func (u *ResolvePublicArticleUseCase) ByLangSlug(query PublicLookupQuery) (PublicArticle, error) {
|
||||
tag, err := language.Normalize(query.Lang)
|
||||
if errors.Is(err, language.ErrInvalidLanguage) {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
unsupported := errors.Is(err, language.ErrUnsupportedLanguage)
|
||||
owner, err := u.sites.GetByKey(query.SiteKey)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
if unsupported {
|
||||
return PublicArticle{}, article.ErrArticleNotFound
|
||||
}
|
||||
found, err := u.articles.GetByLangSlug(owner.ID, tag, query.Slug)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
if !found.IsOnline {
|
||||
return PublicArticle{}, article.ErrArticleNotFound
|
||||
}
|
||||
rows, err := u.localization.Rows(owner.ID)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
versions, err := storyVersions(u.articles, found)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
return u.present(owner, rows, versions, found, query.Format)
|
||||
}
|
||||
|
||||
func (u *ResolvePublicArticleUseCase) ByStory(query PublicStoryQuery) (PublicArticle, error) {
|
||||
requested := ""
|
||||
unsupported := false
|
||||
if query.Lang != nil {
|
||||
tag, err := language.Normalize(*query.Lang)
|
||||
if errors.Is(err, language.ErrInvalidLanguage) {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
requested = tag
|
||||
unsupported = errors.Is(err, language.ErrUnsupportedLanguage)
|
||||
}
|
||||
owner, err := u.sites.GetByKey(query.SiteKey)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
rows, err := u.localization.Rows(owner.ID)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
if unsupported {
|
||||
return PublicArticle{}, article.ErrLanguageNotAvailable
|
||||
}
|
||||
defaultTag := confirmedDefault(rows)
|
||||
if query.Lang == nil {
|
||||
requested = defaultTag
|
||||
}
|
||||
versions, err := u.articles.ListByStory(query.StoryID)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
chosen, fallback, ok := chooseStoryVersion(owner, versions, requested, defaultTag, query.FallbackDefault)
|
||||
if !ok {
|
||||
return PublicArticle{}, article.ErrLanguageNotAvailable
|
||||
}
|
||||
presented, err := u.present(owner, rows, versions, chosen, query.Format)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
presented.RequestedLang = requested
|
||||
presented.Fallback = fallback
|
||||
return presented, nil
|
||||
}
|
||||
|
||||
func chooseStoryVersion(owner site.Site, versions []article.Article, requested string, defaultTag string, fallbackDefault bool) (article.Article, bool, bool) {
|
||||
if requested == "" {
|
||||
return article.Article{}, false, false
|
||||
}
|
||||
if found, ok := publishedVersion(owner, versions, requested); ok {
|
||||
return found, false, true
|
||||
}
|
||||
if fallbackDefault && defaultTag != "" && defaultTag != requested {
|
||||
if found, ok := publishedVersion(owner, versions, defaultTag); ok {
|
||||
return found, true, true
|
||||
}
|
||||
}
|
||||
return article.Article{}, false, false
|
||||
}
|
||||
|
||||
func publishedVersion(owner site.Site, versions []article.Article, tag string) (article.Article, bool) {
|
||||
for _, version := range versions {
|
||||
if version.SiteID == owner.ID && version.Lang == tag && version.IsOnline {
|
||||
return version, true
|
||||
}
|
||||
}
|
||||
return article.Article{}, false
|
||||
}
|
||||
|
||||
func (u *ResolvePublicArticleUseCase) present(owner site.Site, rows []localization.Row, versions []article.Article, found article.Article, format article.BodyFormat) (PublicArticle, error) {
|
||||
rendered, err := renderArticle(u.bodyRenderer, found, format)
|
||||
if err != nil {
|
||||
return PublicArticle{}, err
|
||||
}
|
||||
viewer := ContentCaller{}
|
||||
described := ArticleContext{Capabilities: viewer.capabilitiesFor(found), Translations: []article.Translation{}}
|
||||
return PublicArticle{Article: rendered, Viewer: viewer, Context: described, Alternates: alternatesOf(owner, eligibleSiblings(rows, versions, found))}, nil
|
||||
}
|
||||
Reference in new issue
Block a user