feat: SEO backend data (1/4)

This commit is contained in:
Florian Sylvain committed 2026-10-15 19:30:29 +02:00
1 parent cb1410f0b0
commit 0eb46b0d74
90 files changed
+6094 -350

No files matched your search

+135
View File
@@ -23,6 +23,8 @@ type mockArticleRepo struct {
createCalled bool
credits map[uint32][]uint32
authors *mockAuthorRepo
reportSlugRace bool
createAlwaysRaces bool
}
func newMockArticleRepo() *mockArticleRepo {
@@ -193,11 +195,107 @@ func (m *mockArticleRepo) GetAll() []article.Article {
return result
}
func (m *mockArticleRepo) slugUsedOn(siteID uint32, slug string, exceptID uint32) bool {
if slug == "" {
return false
}
for id, other := range m.articles {
if id != exceptID && other.SiteID == siteID && other.Slug == slug {
return true
}
}
return false
}
func (m *mockArticleRepo) SlugTaken(siteID uint32, slug string, exceptID uint32) (bool, error) {
if err := m.fail.trigger("SlugTaken"); err != nil {
return false, err
}
return m.slugUsedOn(siteID, slug, exceptID), nil
}
func (m *mockArticleRepo) FindByImage(imageID uint32) ([]article.Article, error) {
if err := m.fail.trigger("FindByImage"); err != nil {
return nil, err
}
result := make([]article.Article, 0)
for _, id := range m.sortedIds() {
if a := m.articles[id]; a.UsesImage(imageID) {
result = append(result, a)
}
}
return result, nil
}
func (m *mockArticleRepo) ClearImage(imageID uint32) (article.ImageReferences, error) {
if err := m.fail.trigger("ClearImage"); err != nil {
return article.ImageReferences{}, err
}
references := article.ImageReferences{Cover: []uint32{}, Social: []uint32{}}
for _, id := range m.sortedIds() {
a := m.articles[id]
if a.CoverImageID != nil && *a.CoverImageID == imageID {
a.CoverImageID = nil
references.Cover = append(references.Cover, id)
}
if a.SocialImageID != nil && *a.SocialImageID == imageID {
a.SocialImageID = nil
references.Social = append(references.Social, id)
}
m.articles[id] = a
}
return references, nil
}
func (m *mockArticleRepo) RestoreImage(references article.ImageReferences, imageID uint32) error {
for _, id := range references.Cover {
a := m.articles[id]
a.CoverImageID = idPointer(imageID)
m.articles[id] = a
}
for _, id := range references.Social {
a := m.articles[id]
a.SocialImageID = idPointer(imageID)
m.articles[id] = a
}
return nil
}
func (m *mockArticleRepo) RepointImage(fromImageIDs []uint32, toImageID uint32) ([]uint32, error) {
if err := m.fail.trigger("RepointImage"); err != nil {
return nil, err
}
changed := []uint32{}
for _, id := range m.sortedIds() {
a := m.articles[id]
touched := false
if a.CoverImageID != nil && containsID(fromImageIDs, *a.CoverImageID) {
a.CoverImageID = idPointer(toImageID)
touched = true
}
if a.SocialImageID != nil && containsID(fromImageIDs, *a.SocialImageID) {
a.SocialImageID = idPointer(toImageID)
touched = true
}
if touched {
m.articles[id] = a
changed = append(changed, id)
}
}
return changed, nil
}
func (m *mockArticleRepo) Create(a article.Article) (article.Article, error) {
m.createCalled = true
if err := m.fail.trigger("Create"); err != nil {
return article.Article{}, err
}
if m.createAlwaysRaces {
return article.Article{}, article.ErrSlugTaken
}
if m.slugUsedOn(a.SiteID, a.Slug, 0) {
return article.Article{}, article.ErrSlugTaken
}
m.nextID++
a.ID = m.nextID
m.articles[a.ID] = a
@@ -244,6 +342,16 @@ func (m *mockArticleRepo) Update(id uint32, update article.Update) (article.Arti
if update.SiteID != nil {
a.SiteID = *update.SiteID
}
if update.Slug != nil {
a.Slug = *update.Slug
}
if m.reportSlugRace && (update.Slug != nil || update.SiteID != nil) {
return article.Article{}, article.ErrSlugTaken
}
if m.slugUsedOn(a.SiteID, a.Slug, id) {
return article.Article{}, article.ErrSlugTaken
}
applySeoUpdate(&a, update)
if update.AuthorIDs != nil {
m.credits[id] = append([]uint32{}, *update.AuthorIDs...)
}
@@ -251,6 +359,33 @@ func (m *mockArticleRepo) Update(id uint32, update article.Update) (article.Arti
return m.withAuthors(a), nil
}
func applySeoUpdate(a *article.Article, update article.Update) {
assign := func(target *string, value *string) {
if value != nil {
*target = *value
}
}
assign(&a.Excerpt, update.Excerpt)
assign(&a.SEOTitle, update.SEOTitle)
assign(&a.SEODescription, update.SEODescription)
assign(&a.CoverImageAlt, update.CoverImageAlt)
assign(&a.SocialTitle, update.SocialTitle)
assign(&a.SocialDescription, update.SocialDescription)
assign(&a.CanonicalURL, update.CanonicalURL)
if update.CoverImageID.Set {
a.CoverImageID = update.CoverImageID.Value
}
if update.SocialImageID.Set {
a.SocialImageID = update.SocialImageID.Value
}
if update.Noindex != nil {
a.Noindex = *update.Noindex
}
if update.SEODismissed != nil {
a.SEODismissed = append([]string{}, *update.SEODismissed...)
}
}
func (m *mockArticleRepo) UpdateIsOnline(id uint32, isOnline bool) (article.Article, error) {
if err := m.fail.trigger("UpdateIsOnline"); err != nil {
return article.Article{}, err