245 lines
6.9 KiB
Go
245 lines
6.9 KiB
Go
package persistence
|
|
|
|
import (
|
|
domain "RenewCMS/internal/domain/site"
|
|
"RenewCMS/internal/infrastructure/persistence/mappers"
|
|
entity "RenewCMS/internal/infrastructure/persistence/models"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
type SiteRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewSiteRepository(db *gorm.DB) *SiteRepository {
|
|
return &SiteRepository{db}
|
|
}
|
|
|
|
type articleTotal struct {
|
|
SiteID uint32
|
|
Total int
|
|
}
|
|
|
|
func (s *SiteRepository) articleCounts(db *gorm.DB) (map[uint32]int, error) {
|
|
var totals []articleTotal
|
|
err := db.Model(&entity.Article{}).Select("site_id, COUNT(*) AS total").Group("site_id").Scan(&totals).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
counts := make(map[uint32]int, len(totals))
|
|
for _, total := range totals {
|
|
counts[total.SiteID] = total.Total
|
|
}
|
|
return counts, nil
|
|
}
|
|
|
|
func (s *SiteRepository) articleCount(db *gorm.DB, id uint32) (int, error) {
|
|
var total int64
|
|
if err := db.Model(&entity.Article{}).Where("site_id = ?", id).Count(&total).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return int(total), nil
|
|
}
|
|
|
|
func (s *SiteRepository) List() ([]domain.Site, error) {
|
|
var found []entity.Site
|
|
if err := s.db.Order("is_default DESC, LOWER(name) ASC, id ASC").Find(&found).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
counts, err := s.articleCounts(s.db)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sites := make([]domain.Site, 0, len(found))
|
|
for _, model := range found {
|
|
sites = append(sites, mappers.SiteToDomain(model, counts[model.ID]))
|
|
}
|
|
return sites, nil
|
|
}
|
|
|
|
func (s *SiteRepository) load(db *gorm.DB, model entity.Site) (domain.Site, error) {
|
|
total, err := s.articleCount(db, model.ID)
|
|
if err != nil {
|
|
return domain.Site{}, err
|
|
}
|
|
return mappers.SiteToDomain(model, total), nil
|
|
}
|
|
|
|
func (s *SiteRepository) Get(id uint32) (domain.Site, error) {
|
|
var model entity.Site
|
|
if err := s.db.First(&model, id).Error; err != nil {
|
|
return domain.Site{}, mapNotFoundErr(err, domain.ErrSiteNotFound)
|
|
}
|
|
return s.load(s.db, model)
|
|
}
|
|
|
|
func (s *SiteRepository) GetByKey(key string) (domain.Site, error) {
|
|
var model entity.Site
|
|
if err := s.db.Where("site_key = ?", key).First(&model).Error; err != nil {
|
|
return domain.Site{}, mapNotFoundErr(err, domain.ErrSiteNotFound)
|
|
}
|
|
return s.load(s.db, model)
|
|
}
|
|
|
|
func (s *SiteRepository) GetDefault() (domain.Site, error) {
|
|
var model entity.Site
|
|
if err := s.db.Where("is_default = ?", true).Order("id ASC").First(&model).Error; err != nil {
|
|
return domain.Site{}, mapNotFoundErr(err, domain.ErrSiteNotFound)
|
|
}
|
|
return s.load(s.db, model)
|
|
}
|
|
|
|
func (s *SiteRepository) Create(site domain.Site) (domain.Site, error) {
|
|
model := mappers.SiteToModel(site)
|
|
model.ID = 0
|
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
|
if model.IsDefault {
|
|
if err := lockAllSites(tx); err != nil {
|
|
return err
|
|
}
|
|
if err := unflagDefaults(tx, 0); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return tx.Create(&model).Error
|
|
})
|
|
if err != nil {
|
|
return domain.Site{}, s.explainKeyFailure(model.Key, err)
|
|
}
|
|
return mappers.SiteToDomain(model, 0), nil
|
|
}
|
|
|
|
func (s *SiteRepository) explainKeyFailure(key string, cause error) error {
|
|
if _, err := s.GetByKey(key); err == nil {
|
|
return domain.ErrSiteKeyTaken
|
|
}
|
|
return cause
|
|
}
|
|
|
|
func lockAllSites(tx *gorm.DB) error {
|
|
var locked []entity.Site
|
|
return tx.Clauses(clause.Locking{Strength: "UPDATE"}).Order("id ASC").Find(&locked).Error
|
|
}
|
|
|
|
func lockSite(tx *gorm.DB, id uint32) (entity.Site, error) {
|
|
var locked entity.Site
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&locked, id).Error; err != nil {
|
|
return entity.Site{}, mapNotFoundErr(err, domain.ErrSiteNotFound)
|
|
}
|
|
return locked, nil
|
|
}
|
|
|
|
func unflagDefaults(tx *gorm.DB, exceptID uint32) error {
|
|
return tx.Model(&entity.Site{}).Where("is_default = ? AND id <> ?", true, exceptID).UpdateColumn("is_default", false).Error
|
|
}
|
|
|
|
func (s *SiteRepository) Update(site domain.Site) (domain.Site, error) {
|
|
changes := map[string]any{
|
|
"name": site.Name,
|
|
"base_url": site.BaseURL,
|
|
"default_social_image_id": site.DefaultSocialImageID,
|
|
"default_robots": site.DefaultRobots,
|
|
"locale": site.Locale,
|
|
"site_name": site.SiteName,
|
|
"title_template": site.TitleTemplate,
|
|
"default_description": site.DefaultDescription,
|
|
}
|
|
if _, err := s.Get(site.ID); err != nil {
|
|
return domain.Site{}, err
|
|
}
|
|
if err := s.db.Model(&entity.Site{}).Where("id = ?", site.ID).Updates(changes).Error; err != nil {
|
|
return domain.Site{}, err
|
|
}
|
|
return s.Get(site.ID)
|
|
}
|
|
|
|
func (s *SiteRepository) SetDefault(id uint32) (domain.Site, error) {
|
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := lockAllSites(tx); err != nil {
|
|
return err
|
|
}
|
|
if _, err := lockSite(tx, id); err != nil {
|
|
return err
|
|
}
|
|
if err := unflagDefaults(tx, id); err != nil {
|
|
return err
|
|
}
|
|
return tx.Model(&entity.Site{}).Where("id = ?", id).UpdateColumn("is_default", true).Error
|
|
})
|
|
if err != nil {
|
|
return domain.Site{}, err
|
|
}
|
|
return s.Get(id)
|
|
}
|
|
|
|
func (s *SiteRepository) DeleteMovingArticles(id uint32, reassignTo *uint32) error {
|
|
return s.db.Transaction(func(tx *gorm.DB) error {
|
|
target, err := lockSite(tx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if target.IsDefault {
|
|
return domain.ErrDefaultSite
|
|
}
|
|
remaining, err := s.articleCount(tx, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if remaining > 0 && reassignTo == nil {
|
|
return domain.ErrSiteHasArticles
|
|
}
|
|
if reassignTo != nil {
|
|
if err := moveArticles(tx, id, *reassignTo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return deleteOrNotFound(tx.Delete(&entity.Site{}, id), domain.ErrSiteNotFound)
|
|
})
|
|
}
|
|
|
|
func moveArticles(tx *gorm.DB, from uint32, to uint32) error {
|
|
if _, err := lockSite(tx, to); err != nil {
|
|
return err
|
|
}
|
|
return tx.Unscoped().Model(&entity.Article{}).Where("site_id = ?", from).UpdateColumn("site_id", to).Error
|
|
}
|
|
|
|
func (s *SiteRepository) ClearImage(imageID uint32) ([]uint32, error) {
|
|
ids := []uint32{}
|
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Model(&entity.Site{}).Where("default_social_image_id = ?", imageID).Order("id ASC").Pluck("id", &ids).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(ids) == 0 {
|
|
return nil
|
|
}
|
|
return tx.Model(&entity.Site{}).Where("id IN ?", ids).UpdateColumn("default_social_image_id", nil).Error
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
func (s *SiteRepository) AssignImage(siteIDs []uint32, imageID uint32) error {
|
|
if len(siteIDs) == 0 {
|
|
return nil
|
|
}
|
|
return s.db.Model(&entity.Site{}).Where("id IN ?", siteIDs).UpdateColumn("default_social_image_id", imageID).Error
|
|
}
|
|
|
|
func (s *SiteRepository) RepointImage(fromImageIDs []uint32, toImageID uint32) (int, error) {
|
|
if len(fromImageIDs) == 0 {
|
|
return 0, nil
|
|
}
|
|
result := s.db.Model(&entity.Site{}).Where("default_social_image_id IN ?", fromImageIDs).UpdateColumn("default_social_image_id", toImageID)
|
|
if result.Error != nil {
|
|
return 0, result.Error
|
|
}
|
|
return int(result.RowsAffected), nil
|
|
}
|
|
|
|
var _ domain.Repository = &SiteRepository{}
|