feat: Users API, ownership transfer and removal of the legacy role string

This commit is contained in:
Florian Sylvain committed 2026-10-27 03:08:15 +01:00
1 parent 094df63a7f
commit 80f7037d3a
124 files changed
+4644 -824

No files matched your search

@@ -145,8 +145,8 @@ func (r *RoleRepository) Delete(id uint32) error {
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&entity.Role{}, id).Error; err != nil {
return mapNotFoundErr(err, role.ErrRoleNotFound)
}
var members int64
if err := tx.Model(&entity.User{}).Where("role_id = ?", id).Count(&members).Error; err != nil {
members, err := countMembers(tx, id)
if err != nil {
return err
}
if members > 0 {
@@ -159,24 +159,68 @@ func (r *RoleRepository) Delete(id uint32) error {
})
}
func (r *RoleRepository) ReassignMembers(from uint32, to uint32) (int, error) {
moved := r.db.Model(&entity.User{}).
Where("role_id = ? AND EXISTS (SELECT 1 FROM roles WHERE id = ?)", from, to).
UpdateColumn("role_id", to)
if moved.Error != nil {
return 0, moved.Error
}
if moved.RowsAffected > 0 {
return int(moved.RowsAffected), nil
}
var targets int64
if err := r.db.Model(&entity.Role{}).Where("id = ?", to).Count(&targets).Error; err != nil {
func countMembers(tx *gorm.DB, id uint32) (int64, error) {
var users, invites int64
if err := tx.Model(&entity.User{}).Where("role_id = ?", id).Count(&users).Error; err != nil {
return 0, err
}
if targets == 0 {
return 0, role.ErrInvalidReassign
if err := tx.Model(&entity.Invite{}).Where("role_id = ? AND accepted_at IS NULL", id).Count(&invites).Error; err != nil {
return 0, err
}
return 0, nil
return users + invites, nil
}
type memberRow struct {
RoleID uint32
Members int
}
func (r *RoleRepository) MemberCounts() (map[uint32]int, error) {
var users, invites []memberRow
if err := r.db.Model(&entity.User{}).Select("role_id, COUNT(*) AS members").Where("role_id IS NOT NULL").Group("role_id").Scan(&users).Error; err != nil {
return nil, err
}
if err := r.db.Model(&entity.Invite{}).Select("role_id, COUNT(*) AS members").Where("role_id IS NOT NULL AND accepted_at IS NULL").Group("role_id").Scan(&invites).Error; err != nil {
return nil, err
}
counts := map[uint32]int{}
for _, row := range append(users, invites...) {
if row.RoleID != 0 {
counts[row.RoleID] += row.Members
}
}
return counts, nil
}
func (r *RoleRepository) ReassignMembers(from uint32, to uint32) (int, error) {
moved := 0
err := r.db.Transaction(func(tx *gorm.DB) error {
target := "EXISTS (SELECT 1 FROM roles WHERE id = ?)"
users := tx.Model(&entity.User{}).Where("role_id = ? AND "+target, from, to).UpdateColumn("role_id", to)
if users.Error != nil {
return users.Error
}
invites := tx.Model(&entity.Invite{}).Where("role_id = ? AND accepted_at IS NULL AND "+target, from, to).UpdateColumn("role_id", to)
if invites.Error != nil {
return invites.Error
}
moved = int(users.RowsAffected + invites.RowsAffected)
if moved > 0 {
return nil
}
var targets int64
if err := tx.Model(&entity.Role{}).Where("id = ?", to).Count(&targets).Error; err != nil {
return err
}
if targets == 0 {
return role.ErrInvalidReassign
}
return nil
})
if err != nil {
return 0, err
}
return moved, nil
}
func (r *RoleRepository) RemoveTypedGrants(typeKey string) (int, error) {