package acl import ( "errors" "fmt" "strings" "gorm.io/gorm" "gorm.io/gorm/clause" ) // GetResources returns all resources. // May return [ErrNotInitialized] or db error. func (s *Service) GetResources() ([]Resource, error) { if !s.isInitialized() { return nil, ErrNotInitialized } var resources []Resource if err := s.db.Order("id").Find(&resources).Error; err != nil { return nil, fmt.Errorf("db error: %w", err) } return resources, nil } // CreateResource creates a new resource with the given key or returns existing one. // Returns ID of created resource. // May return [ErrNotInitialized], [ErrInvalidResourceKey], [ErrResourceAlreadyExists] or db error. func (s *Service) CreateResource(key string) (uint, error) { if !s.isInitialized() { return 0, ErrNotInitialized } key = strings.TrimSpace(key) if key == "" { return 0, ErrInvalidResourceKey } var res Resource if err := s.db.Where("key = ?", key).First(&res).Error; err == nil { // already exists return res.ID, ErrResourceAlreadyExists } else if !errors.Is(err, gorm.ErrRecordNotFound) { // other db error return 0, fmt.Errorf("db error: %w", err) } res = Resource{Key: key} if err := s.db.Create(&res).Error; err != nil { return 0, fmt.Errorf("db error: %w", err) } return res.ID, nil } // GetResourceByID returns the resource with the given ID. // May return [ErrNotInitialized], [ErrResourceNotFound] or db error. func (s *Service) GetResourceByID(resourceID uint) (*Resource, error) { if !s.isInitialized() { return nil, ErrNotInitialized } var res Resource if err := s.db.First(&res, resourceID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrResourceNotFound } return nil, fmt.Errorf("db error: %w", err) } return &res, nil } // UpdateResource updates the key of a resource. // May return [ErrNotInitialized], [ErrInvalidResourceKey], [ErrResourceNotFound], [ErrSameResourceKey] or db error. func (s *Service) UpdateResource(resourceID uint, newKey string) error { if !s.isInitialized() { return ErrNotInitialized } newKey = strings.TrimSpace(newKey) if newKey == "" { return ErrInvalidResourceKey } var res Resource if err := s.db.First(&res, resourceID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrResourceNotFound } return fmt.Errorf("db error: %w", err) } // same key? if res.Key == newKey { return ErrSameResourceKey } // check if key used by another resource var count int64 if err := s.db.Model(&Resource{}). Where("key = ? AND id != ?", newKey, resourceID). Count(&count).Error; err != nil { return fmt.Errorf("db error: %w", err) } if count > 0 { return ErrSameResourceKey } res.Key = newKey if err := s.db.Save(&res).Error; err != nil { return fmt.Errorf("failed to update resource: %w", err) } return nil } // DeleteResource deletes a resource. // May return [ErrNotInitialized], [ErrResourceNotFound], [ErrResourceInUse] or db error. func (s *Service) DeleteResource(resourceID uint) error { if !s.isInitialized() { return ErrNotInitialized } result := s.db.Delete(&Resource{}, resourceID) if err := result.Error; err != nil { if strings.Contains(err.Error(), "FOREIGN KEY constraint failed") { return ErrResourceInUse } return fmt.Errorf("db error: %w", err) } if result.RowsAffected == 0 { return ErrResourceNotFound } return nil } // AssignResourceToRole assigns a resource to a role // May return [ErrNotInitialized], [ErrRoleNotFound], [ErrResourceNotFound], [ErrAlreadyAssigned] or db error. func (s *Service) AssignResourceToRole(roleID, resourceID uint) error { if !s.isInitialized() { return ErrNotInitialized } // check role exists var r Role if err := s.db.First(&r, roleID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrRoleNotFound } return fmt.Errorf("failed to fetch role: %w", err) } // check resource exists var res Resource if err := s.db.First(&res, resourceID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrResourceNotFound } return fmt.Errorf("failed to fetch resource: %w", err) } rr := RoleResource{ RoleID: roleID, ResourceID: resourceID, } tx := s.db.Clauses(clause.OnConflict{DoNothing: true}).Create(&rr) if tx.Error != nil { return fmt.Errorf("failed to assign resource to role: %w", tx.Error) } // if nothing inserted — already assigned if tx.RowsAffected == 0 { return ErrResourceAlreadyAssigned } return nil } // RemoveResourceFromRole removes a resource from a role // May return [ErrNotInitialized], [ErrRoleNotFound], [ErrResourceNotFound], [ErrRoleResourceNotFound] or db error. func (s *Service) RemoveResourceFromRole(roleID, resourceID uint) error { if !s.isInitialized() { return ErrNotInitialized } // check role exists var r Role if err := s.db.First(&r, roleID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrRoleNotFound } return fmt.Errorf("failed to fetch role: %w", err) } // check resource exists var res Resource if err := s.db.First(&res, resourceID).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return ErrResourceNotFound } return fmt.Errorf("failed to fetch resource: %w", err) } tx := s.db.Where("role_id = ? AND resource_id = ?", roleID, resourceID).Delete(&RoleResource{}) if tx.Error != nil { return fmt.Errorf("failed to remove resource from role: %w", tx.Error) } if tx.RowsAffected == 0 { return ErrRoleResourceNotFound } return nil }