feat: Users API, ownership transfer and removal of the legacy role string
This commit is contained in:
1 parent
094df63a7f
commit
80f7037d3a
124 files changed
+4644
-824
No files matched your search
@@ -1,7 +1,7 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/domain/role"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -9,8 +9,15 @@ import (
|
||||
|
||||
func BackfillCreatedBy(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
ownerRoleID, seeded, err := builtInRoleID(tx, role.OwnerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !seeded {
|
||||
return nil
|
||||
}
|
||||
var owner entity.User
|
||||
result := tx.Where("role = ?", string(domainUser.RoleOwner)).Order("id asc").Limit(1).Find(&owner)
|
||||
result := tx.Where("role_id = ?", ownerRoleID).Order("id asc").Limit(1).Find(&owner)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/role"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func BackfillInviteRoles(db *gorm.DB) error {
|
||||
if !legacyRoleColumnExists(db) {
|
||||
return nil
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
done, err := markerApplied(tx, inviteRolesMarker)
|
||||
if err != nil || done {
|
||||
return err
|
||||
}
|
||||
adminID, err := requireBuiltInRoleID(tx, role.AdminName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&entity.Invite{}).Where("role_id IS NULL OR role_id = 0").UpdateColumn("role_id", adminID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return applyMarker(tx, inviteRolesMarker)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/role"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
const (
|
||||
usersTable = "users"
|
||||
legacyRoleColumn = "role"
|
||||
legacyOwnerValue = "owner"
|
||||
legacyAdminValue = "admin"
|
||||
legacyUnsetValue = ""
|
||||
unassignedRoleCond = "(role_id IS NULL OR role_id = 0)"
|
||||
)
|
||||
|
||||
func legacyRoleColumnExists(db *gorm.DB) bool {
|
||||
return db.Migrator().HasColumn(usersTable, legacyRoleColumn)
|
||||
}
|
||||
|
||||
func requireBuiltInRoleID(tx *gorm.DB, name string) (uint32, error) {
|
||||
id, found, err := builtInRoleID(tx, name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !found {
|
||||
return 0, role.ErrRoleNotFound
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func BackfillLegacyUserRoles(db *gorm.DB) error {
|
||||
if !legacyRoleColumnExists(db) {
|
||||
return nil
|
||||
}
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
done, err := markerApplied(tx, legacyUserRolesMarker)
|
||||
if err != nil || done {
|
||||
return err
|
||||
}
|
||||
ownerID, err := requireBuiltInRoleID(tx, role.OwnerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
adminID, err := requireBuiltInRoleID(tx, role.AdminName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writerID, err := requireBuiltInRoleID(tx, role.WriterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
legacy := clause.Column{Name: legacyRoleColumn}
|
||||
assign := func(roleID uint32, condition string, args ...any) error {
|
||||
return tx.Table(usersTable).Where(unassignedRoleCond).Where(condition, args...).UpdateColumn("role_id", roleID).Error
|
||||
}
|
||||
var owners []uint32
|
||||
if err := tx.Table(usersTable).Where(unassignedRoleCond).Where("? = ?", legacy, legacyOwnerValue).Order("id asc").Pluck("id", &owners).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(owners) > 0 {
|
||||
if err := assign(ownerID, "id = ?", owners[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := assign(adminID, "? = ?", legacy, legacyOwnerValue); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assign(adminID, "? = ?", legacy, legacyAdminValue); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(owners) == 0 {
|
||||
var lowest []uint32
|
||||
if err := tx.Model(&entity.User{}).Order("id asc").Limit(1).Pluck("id", &lowest).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if len(lowest) > 0 {
|
||||
if err := assign(ownerID, "? = ? AND id = ?", legacy, legacyUnsetValue, lowest[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := assign(adminID, "? = ?", legacy, legacyUnsetValue); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assign(writerID, "1 = 1"); err != nil {
|
||||
return err
|
||||
}
|
||||
return applyMarker(tx, legacyUserRolesMarker)
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,7 @@ 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{}, &models.Role{}, &models.RolePermission{})
|
||||
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{}, &models.MigrationMarker{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -17,10 +17,10 @@ func Migrate(db *gorm.DB, publicURL string) error {
|
||||
if err := SeedBuiltInRoles(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BackfillUserRoles(db); err != nil {
|
||||
if err := BackfillLegacyUserRoles(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BackfillUserRoleIDs(db); err != nil {
|
||||
if err := BackfillInviteRoles(db); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := BackfillCreatedBy(db); err != nil {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
legacyUserRolesMarker = "legacy_user_roles"
|
||||
inviteRolesMarker = "invite_roles"
|
||||
)
|
||||
|
||||
func markerApplied(tx *gorm.DB, name string) (bool, error) {
|
||||
var count int64
|
||||
if err := tx.Model(&entity.MigrationMarker{}).Where("name = ?", name).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func applyMarker(tx *gorm.DB, name string) error {
|
||||
return tx.Create(&entity.MigrationMarker{Name: name, AppliedAt: time.Now()}).Error
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func BackfillUserRoles(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
var firstUser entity.User
|
||||
result := tx.Where("role = ''").Order("id asc").Limit(1).Find(&firstUser)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var lowest entity.User
|
||||
if err := tx.Order("id asc").First(&lowest).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if lowest.Role == "" {
|
||||
if err := tx.Model(&entity.User{}).Where("id = ?", lowest.ID).Update("role", string(domainUser.RoleOwner)).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Model(&entity.User{}).Where("role = ''").Update("role", string(domainUser.RoleAdmin)).Error
|
||||
})
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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