package acl import ( "errors" "fmt" "strings" "gorm.io/gorm" ) // GetRoles returns all roles. // May return [ErrNotInitialized] or db error. func (s *Service) GetRoles() ([]Role, error) { if !s.isInitialized() { return nil, ErrNotInitialized } var roles []Role if err := s.db.Preload("Resources").Preload("Users").Order("id").Find(&roles).Error; err != nil { return nil, fmt.Errorf("db error: %w", err) } return roles, nil } // CreateRole creates a new role with the given name or returns existing one. // Returns the ID of the created role. // May return [ErrNotInitialized], [ErrInvalidRoleName], [ErrRoleAlreadyExists] or db error. func (s *Service) CreateRole(name string) (uint, error) { if !s.isInitialized() { return 0, ErrNotInitialized } name = strings.TrimSpace(name) if name == "" { return 0, ErrInvalidRoleName } var role Role if err := s.db.Where("name = ?", name).First(&role).Error; err == nil { // already exists return role.ID, ErrRoleAlreadyExists } else if !errors.Is(err, gorm.ErrRecordNotFound) { // other database error return 0, fmt.Errorf("db error: %w", err) } role = Role{Name: name} if err := s.db.Create(&role).Error; err != nil { return 0, fmt.Errorf("db error: %w", err) } return role.ID, nil } // GetRoleByID returns the role with the given ID or an error. // May return [ErrNotInitialized], [ErrRoleNotFound] or db error. func (s *Service) GetRoleByID(roleID uint) (*Role, error) { if !s.isInitialized() { return nil, ErrNotInitialized } var role Role err := s.db.Preload("Resources").Preload("Users").First(&role, roleID).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrRoleNotFound } return nil, fmt.Errorf("db error: %w", err) } return &role, nil } // UpdateRole updates the name of a role. // May return [ErrNotInitialized], [ErrInvalidRoleName], [ErrRoleNotFound], [ErrSameRoleName], or db error. func (s *Service) UpdateRole(roleID uint, newName string) error { if !s.isInitialized() { return ErrNotInitialized } newName = strings.TrimSpace(newName) if newName == "" { return ErrInvalidRoleName } var role Role err := s.db.First(&role, roleID).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrRoleNotFound } return fmt.Errorf("db error: %w", err) } // check for name conflicts if role.Name == newName { return ErrSameRoleName } var count int64 err = s.db.Model(&Role{}).Where("name = ? AND id != ?", newName, roleID).Count(&count).Error if err != nil { return fmt.Errorf("db error: %w", err) } if count > 0 { return ErrSameRoleName } role.Name = newName if err := s.db.Save(&role).Error; err != nil { return fmt.Errorf("failed to update role: %w", err) } return nil } // DeleteRole deletes a role. // May return [ErrNotInitialized], [ErrRoleNotFound], [ErrRoleInUse] or db error. func (s *Service) DeleteRole(roleID uint) error { if !s.isInitialized() { return ErrNotInitialized } result := s.db.Delete(&Role{}, roleID) if err := result.Error; err != nil { if strings.Contains(err.Error(), "FOREIGN KEY constraint failed") { return ErrRoleInUse } return fmt.Errorf("db error: %w", err) } if result.RowsAffected == 0 { return ErrRoleNotFound } return nil }