feat: roles system backend
This commit is contained in:
1 parent
44cca46d5e
commit
8c6c6456da
50 files changed
+2578
-22
No files matched your search
@@ -0,0 +1,25 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func BackfillCreatedBy(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
var owner entity.User
|
||||
result := tx.Where("role = ?", string(domainUser.RoleOwner)).Order("id asc").Limit(1).Find(&owner)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := tx.Unscoped().Model(&entity.Article{}).Where("created_by = 0").UpdateColumn("created_by", owner.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Model(&entity.Entry{}).Where("created_by = 0").UpdateColumn("created_by", owner.ID).Error
|
||||
})
|
||||
}
|
||||
@@ -7,16 +7,25 @@ import (
|
||||
)
|
||||
|
||||
func Migrate(db *gorm.DB, publicURL string) error {
|
||||
err := tableOptionsFor(db).AutoMigrate(&models.Article{}, &models.Image{}, &models.User{}, &models.Invite{}, &models.APIKey{}, &models.PurgeSettings{}, &models.AuditEvent{}, &models.AuditSettings{}, &models.ImageVariant{}, &models.ImageVariantFile{}, &models.ImageVariantRejectionDay{}, &models.Site{}, &models.Author{}, &models.ArticleAuthor{}, &models.ContentType{}, &models.Entry{}, &models.RevokedSession{})
|
||||
err := tableOptionsFor(db).AutoMigrate(&models.Article{}, &models.Image{}, &models.User{}, &models.Invite{}, &models.APIKey{}, &models.PurgeSettings{}, &models.AuditEvent{}, &models.AuditSettings{}, &models.ImageVariant{}, &models.ImageVariantFile{}, &models.ImageVariantRejectionDay{}, &models.Site{}, &models.Author{}, &models.ArticleAuthor{}, &models.ContentType{}, &models.Entry{}, &models.RevokedSession{}, &models.Role{}, &models.RolePermission{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureImagePathIndex(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SeedBuiltInRoles(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BackfillUserRoles(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BackfillUserRoleIDs(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BackfillCreatedBy(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SeedDefaultSite(db, publicURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"RenewCMS/internal/domain/role"
|
||||
"RenewCMS/internal/infrastructure/persistence/mappers"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func SeedBuiltInRoles(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
for _, builtIn := range role.BuiltInRoles() {
|
||||
if err := seedBuiltInRole(tx, builtIn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func seedBuiltInRole(tx *gorm.DB, builtIn role.Role) error {
|
||||
id, found, err := builtInRoleID(tx, builtIn.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if found {
|
||||
return reconcileBuiltInRole(tx, id, builtIn)
|
||||
}
|
||||
model := mappers.RoleToModel(builtIn)
|
||||
if err := tx.Create(&model).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Create(mappers.GrantsToModel(model.ID, builtIn.Grants)).Error
|
||||
}
|
||||
|
||||
func builtInRoleID(tx *gorm.DB, name string) (uint32, bool, error) {
|
||||
var found entity.Role
|
||||
result := tx.Where("name = ? AND built_in = ?", name, true).Order("id asc").Limit(1).Find(&found)
|
||||
if result.Error != nil {
|
||||
return 0, false, result.Error
|
||||
}
|
||||
return found.ID, result.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
func reconcileBuiltInRole(tx *gorm.DB, id uint32, builtIn role.Role) error {
|
||||
var stored entity.Role
|
||||
if err := tx.First(&stored, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if !builtIn.Locked && stored.Version != 1 {
|
||||
return nil
|
||||
}
|
||||
var present []string
|
||||
if err := tx.Model(&entity.RolePermission{}).Where("role_id = ?", id).Pluck("permission_key", &present).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
missing := []role.Grant{}
|
||||
for _, grant := range builtIn.Grants {
|
||||
if !slices.Contains(present, grant.Key) {
|
||||
missing = append(missing, grant)
|
||||
}
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tx.Create(mappers.GrantsToModel(id, missing)).Error
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/role"
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var legacyRoleNames = map[domainUser.Role]string{
|
||||
domainUser.RoleOwner: role.OwnerName,
|
||||
domainUser.RoleAdmin: role.AdminName,
|
||||
}
|
||||
|
||||
func BackfillUserRoleIDs(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
for legacy, name := range legacyRoleNames {
|
||||
roleID, found, err := builtInRoleID(tx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
err = tx.Model(&entity.User{}).
|
||||
Where("role = ? AND (role_id IS NULL OR role_id = 0)", string(legacy)).
|
||||
UpdateColumn("role_id", roleID).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user