Files
RenewCMS/internal/infrastructure/persistence/database/articleSlugBackfill.go
T

88 lines
2.2 KiB
Go

package database
import (
"RenewCMS/internal/domain/article"
entity "RenewCMS/internal/infrastructure/persistence/models"
"gorm.io/gorm"
)
const (
articleSlugIndexName = "idx_articles_site_slug"
articleSlugIndexStatement = "CREATE UNIQUE INDEX " + articleSlugIndexName + " ON articles (site_id, slug)"
)
type slugOwner struct {
siteID uint32
slug string
}
type slugAssignment struct {
id uint32
slug string
}
func BackfillArticleSlugs(db *gorm.DB) error {
return db.Transaction(func(tx *gorm.DB) error {
var rows []entity.Article
if err := tx.Unscoped().Select("id", "site_id", "title", "slug", "deleted_at").Order("id ASC").Find(&rows).Error; err != nil {
return err
}
assignments := planSlugAssignments(rows)
for _, assignment := range assignments {
if err := tx.Unscoped().Model(&entity.Article{}).Where("id = ?", assignment.id).UpdateColumn("slug", assignment.slug).Error; err != nil {
return err
}
}
return nil
})
}
func planSlugAssignments(rows []entity.Article) []slugAssignment {
assignments := []slugAssignment{}
claimed := map[slugOwner]bool{}
pending := []entity.Article{}
for _, row := range rows {
if row.DeletedAt.Valid {
if tombstone := article.Tombstone(row.ID); row.Slug != tombstone {
assignments = append(assignments, slugAssignment{row.ID, tombstone})
}
continue
}
owner := slugOwner{row.SiteID, row.Slug}
if row.Slug == "" || claimed[owner] {
pending = append(pending, row)
continue
}
claimed[owner] = true
}
for _, row := range pending {
base := row.Slug
attempt := 2
if base == "" {
base = snapshotSlug(row.Title)
attempt = 1
}
slug := firstFreeSnapshotSlug(claimed, row.SiteID, base, attempt)
claimed[slugOwner{row.SiteID, slug}] = true
assignments = append(assignments, slugAssignment{row.ID, slug})
}
return assignments
}
func firstFreeSnapshotSlug(claimed map[slugOwner]bool, siteID uint32, base string, attempt int) string {
for ; ; attempt++ {
candidate := snapshotSlugWithSuffix(base, attempt)
if !claimed[slugOwner{siteID, candidate}] {
return candidate
}
}
}
func ensureArticleSlugIndex(db *gorm.DB) error {
if db.Migrator().HasIndex(&entity.Article{}, articleSlugIndexName) {
return nil
}
return db.Exec(articleSlugIndexStatement).Error
}