package useCases import ( "RenewCMS/internal/domain/article" "RenewCMS/internal/domain/builtintype" "RenewCMS/internal/domain/language" "RenewCMS/internal/domain/localization" "RenewCMS/internal/domain/role" "errors" "slices" ) type DescribeArticleUseCase struct { articles article.Repository localization localization.Repository } type ArticleContext struct { Capabilities []string Translations []article.Translation Source *article.Source SourceUnavailable bool MissingLangs []string ExcludedFromPublic bool PartialView bool } func NewDescribeArticleUseCase(articles article.Repository, localizationRepository localization.Repository) *DescribeArticleUseCase { return &DescribeArticleUseCase{articles: articles, localization: localizationRepository} } func (d *DescribeArticleUseCase) Describe(art article.Article, caller ContentCaller) (ArticleContext, error) { rows, err := d.localization.Rows(art.SiteID) if err != nil { return ArticleContext{}, err } described := ArticleContext{ Capabilities: caller.capabilitiesFor(art), Translations: []article.Translation{}, MissingLangs: []string{}, ExcludedFromPublic: !localization.PublicTag(rows, art.Lang), PartialView: caller.Grants.ScopeFor(role.KeyContentRead, builtintype.KeyArticle) != role.ScopeAll, } if caller.isAnonymous() { return described, nil } readable, err := d.readableVersions(art, caller) if err != nil { return ArticleContext{}, err } described.Translations = translationsOf(caller, sortByLanguage(readable, rows), rows) described.MissingLangs = missingLanguages(readable, rows, caller.Grants.HoldsFor(role.KeyContentCreate, builtintype.KeyArticle)) described.Source, described.SourceUnavailable, err = d.source(art, caller) return described, err } func (d *DescribeArticleUseCase) source(art article.Article, caller ContentCaller) (*article.Source, bool, error) { if art.SourceArticleID == nil { return nil, false, nil } origin, err := d.articles.Get(*art.SourceArticleID) if errors.Is(err, article.ErrArticleNotFound) || err == nil && !caller.seesArticle(origin) { return nil, true, nil } if err != nil { return nil, false, err } return &article.Source{ArticleID: origin.ID, Revision: art.SourceRevision}, false, nil } func (d *DescribeArticleUseCase) readableVersions(art article.Article, caller ContentCaller) ([]article.Article, error) { if art.StoryID == 0 { return []article.Article{art}, nil } versions, err := d.articles.ListByStory(art.StoryID) if err != nil { return nil, err } return caller.readableVersions(versions), nil } func translationsOf(caller ContentCaller, ordered []article.Article, rows []localization.Row) []article.Translation { translations := make([]article.Translation, 0, len(ordered)) for _, version := range ordered { translations = append(translations, article.Translation{ ID: version.ID, Lang: version.Lang, Title: version.Title, IsOnline: version.IsOnline, LanguageEnabled: version.Lang == language.Unassigned || localization.EnabledTag(rows, version.Lang), ExcludedFromPublic: !localization.PublicTag(rows, version.Lang), SourceArticleID: trackedSourceID(version, ordered), Capabilities: caller.capabilitiesFor(version), }) } return translations } func trackedSourceID(version article.Article, readable []article.Article) *uint32 { if version.SourceArticleID == nil || !slices.ContainsFunc(readable, func(candidate article.Article) bool { return candidate.ID == *version.SourceArticleID }) { return nil } return version.SourceArticleID }