Skip to content
Snippets Groups Projects
Select Git revision
  • 47a4a6f7e954e1b9b8d5e2be0012c8e333352e89
  • master default protected
  • feature/PRXS-3156-SaveRevision
  • PRXS-3421-RecursiveReferences
  • feature/PRXS-NotifyAttachment
  • feature/PRXS-3170-TemplateFuncsFix
  • feature/PRXS-3143-3235-ReferenceOptions
  • feature/PRXS-1869-SuperUser
  • feature/PRXS-3106-NoCache
  • release/0.33
  • feature/2781-SpacesLoggingMiddleware
  • feature/PRXS-3143-LimitReferenceFields
  • feature/PRXS-3234-FeaturePruneIdents
  • feature/3109-SerializeFeature
  • feature/3109-RecoverySchema
  • feature/3109-feature
  • fix/PRXS-3369-ValidateFields
  • refactor/PRXS-3306-MovePkgGroup1
  • refactor/6-pkg-refactor-expr
  • fix/PRXS-3360-TemplateBuilderPatch
  • feature/3293-MongoV2
  • v0.34.0
  • v0.33.2
  • v0.33.1
  • v0.32.0
  • v0.31.1
  • v0.31.0
  • v0.30.0
  • v0.29.0
  • v0.28.0
  • v0.27.0-alpha.1+16
  • v0.27.0-alpha.1+15
  • v0.27.0-alpha.1+14
  • v0.27.0-alpha.1+13
  • v0.27.0-alpha.1+12
  • v0.27.0-alpha.1+11
  • v0.27.0-alpha.1+10
  • v0.27.0-alpha.1+9
  • v0.27.0-alpha.1+8
  • v0.27.0-alpha.1+7
  • v0.27.0-alpha.1+6
41 results

item.go

Blame
  • item.go 9.57 KiB
    package setup
    
    import (
    	"context"
    	"reflect"
    	"strings"
    
    	"go.uber.org/zap"
    
    	"git.perx.ru/perxis/perxis-go/pkg/collections"
    	"git.perx.ru/perxis/perxis-go/pkg/errors"
    	"git.perx.ru/perxis/perxis-go/pkg/items"
    )
    
    var (
    	ErrCheckItems     = errors.New("items check error")
    	ErrInstallItems   = errors.New("failed to install items")
    	ErrUninstallItems = errors.New("failed to uninstall items")
    	ErrItemsNotFound  = errors.New("item not found")
    )
    
    type (
    	Item       = Entity[ItemConf, *items.Item]
    	Items      = EntityList[ItemConf, *items.Item]
    	ItemOption = EntityOption[ItemConf, *items.Item]
    
    	ItemConf struct {
    		PublishFunc func(s *Setup, item *items.Item) (*items.Item, bool)
    		encoded     bool // Если запись загружена из файла, необходимо выполнить Decode перед установкой
    	}
    )
    
    var (
    	NewItem                = NewEntity[ItemConf, *items.Item]
    	OverwriteItem          = Overwrite[ItemConf, *items.Item]
    	KeepExistingItem       = Keep[ItemConf, *items.Item]
    	DeleteItem             = Delete[ItemConf, *items.Item]
    	DeleteItemIfRemoveFlag = DeleteIfRemoveFlag[ItemConf, *items.Item]
    )
    
    func (ItemConf) Init(e *Entity[ItemConf, *items.Item]) {
    	PublishItem()(e)
    	KeepExistingItem()(e)
    	DeleteItemIfRemoveFlag()(e)
    }
    
    // OverwriteItem перезаписывает элемент
    func PublishItem() ItemOption {
    	return func(c *Item) {
    		c.Conf.PublishFunc = func(s *Setup, item *items.Item) (*items.Item, bool) { return item, true }
    	}
    }
    
    // DraftItem не публикует элемент, сохраняет его в черновике
    func DraftItem() ItemOption {
    	return func(c *Item) {
    		c.Conf.PublishFunc = func(s *Setup, item *items.Item) (*items.Item, bool) { return item, false }
    	}
    }
    
    // DecodeItem декодирует элемент перед установкой
    func DecodeItem() ItemOption {
    	return func(c *Item) {
    		c.Conf.encoded = true
    	}
    }
    
    // OverwriteFields разрешает перезаписывать указанные поля элемента, в противном случае считается что элемент не изменился
    func OverwriteFields(fields ...string) ItemOption {
    	return func(c *Item) {
    		c.UpdateFunc = func(s *Setup, old, new *items.Item) (*items.Item, bool) {
    			var changed bool
    
    			for _, field := range fields {
    				// Пропускаем системные поля
    				if items.IsSystemField(field) {
    					continue
    				}
    
    				newValue, err := new.Get(field)
    				if err != nil {
    					continue
    				}
    				oldValue, err := old.Get(field)
    
    				if err != nil || newValue != oldValue {
    					changed = true
    					if err = old.Set(field, newValue); err != nil {
    						return nil, false // не обновляем данные если не удалось установить значение
    					}
    				}
    			}
    
    			return old, changed
    		}
    	}
    }
    
    // KeepFields сохраняет указанные поля элемента
    func KeepFields(fields ...string) ItemOption {
    	return func(c *Item) {
    		c.UpdateFunc = func(s *Setup, old, new *items.Item) (*items.Item, bool) {
    
    			for _, field := range fields {
    				if items.IsSystemField(field) {
    					continue
    				}
    
    				oldValue, err := old.Get(field)
    				if err != nil {
    					continue
    				}
    
    				newValue, err := new.Get(field)
    				if err != nil || newValue != oldValue {
    					if err = new.Set(field, oldValue); err != nil {
    						return nil, false // не обновляем данные если не удалось установить значение
    					}
    				}
    			}
    
    			return new, !reflect.DeepEqual(old, new)
    		}
    	}
    }
    
    //
    // Item Setup
    //
    
    // AddItem добавляет требования к настройке элементов в пространстве
    func (s *Setup) AddItem(item *items.Item, opt ...ItemOption) *Setup {
    	s.Config.Items.Add(item, opt...)
    	return s
    }
    
    // AddItems добавляет требования к настройке элементов в пространстве
    func (s *Setup) AddItems(items []*items.Item, opt ...ItemOption) *Setup {
    	s.Config.Items.AddMany(items, opt...)
    	return s
    }
    
    // InstallItem настраивает элемент
    func (s *Setup) InstallItem(ctx context.Context, exists map[string]*items.Item, c *Item) error {
    	item := c.Value(s)
    	item.SpaceID, item.EnvID = s.SpaceID, s.EnvironmentID
    
    	exist, itemExists := exists[item.ID]
    	// Если элемент не существует, создаем его
    	if !itemExists {
    		if item, publish := c.Conf.PublishFunc(s, item); publish {
    			return items.CreateAndPublishItem(ctx, s.content.Items, item)
    		}
    		if _, err := s.content.Items.Create(ctx, item); err != nil {
    			return errors.Wrap(err, "create item")
    		}
    		return nil
    	}
    
    	// Если элемент существует, обновляем его
    	if item, changed := c.UpdateFunc(s, exist, item); changed {
    		if _, publish := c.Conf.PublishFunc(s, item); publish {
    			return items.UpdateAndPublishItem(ctx, s.content.Items, item)
    		}
    		if err := s.content.Items.Update(ctx, item); err != nil {
    			return errors.Wrap(err, "update item")
    		}
    		if err := s.content.Items.Unpublish(ctx, item); err != nil {
    			return errors.Wrap(err, "unpublish item")
    		}
    		return nil
    	}
    
    	return nil
    }
    
    // InstallItems устанавливает все элементы
    func (s *Setup) InstallItems(ctx context.Context) (err error) {
    	if len(s.Items) == 0 {
    		return nil
    	}
    
    	s.logger.Debug("Installing items", zap.Int("Items", len(s.Items)))
    
    	for collID, itms := range groupByCollection(s.Items) {
    		var coll *collections.Collection
    
    		for i, c := range itms {
    			// Пропускаем элементы, которые не требуют декодирования
    			if !c.Conf.encoded {
    				continue
    			}
    
    			// Получаем коллекцию и схему для декодирования элемента
    			if coll == nil {
    				coll, err = s.content.Collections.Get(ctx, s.SpaceID, s.EnvironmentID, collID)
    				if err != nil {
    					return err
    				}
    			}
    
    			// Декодируем элемент
    			decoded, err := c.value.Decode(ctx, coll.Schema)
    			if err != nil {
    				return err
    			}
    
    			itms[i].value = decoded
    		}
    
    		// Получаем существующие элементы
    		exists, err := s.getItems(ctx, collID, itms)
    		if err != nil {
    			return err
    		}
    
    		// Устанавливаем элементы
    		for _, c := range itms {
    			if err := s.InstallItem(ctx, exists, c); err != nil {
    				item := c.Value(s)
    				s.logger.Error("Failed to install item", zap.String("ID", item.ID),
    					zap.String("Collection", item.CollectionID), zap.Error(err))
    				return errors.WithDetailf(errors.Wrap(err, "failed to install item"),
    					"Возникла ошибка при добавлении элемента %s(%s)", item.ID, item.CollectionID)
    			}
    		}
    	}
    
    	return nil
    }
    
    // UninstallItem удаляет элемент
    func (s *Setup) UninstallItem(ctx context.Context, c *Item) error {
    	item := c.Value(s)
    	if c.DeleteFunc(s, item) {
    		err := s.content.Items.Delete(ctx, &items.Item{SpaceID: s.SpaceID, EnvID: s.EnvironmentID, CollectionID: item.CollectionID, ID: item.ID})
    		if err != nil && !strings.Contains(err.Error(), items.ErrNotFound.Error()) {
    			return err
    		}
    	}
    	return nil
    }
    
    func (s *Setup) UninstallItems(ctx context.Context) error {
    	if len(s.Items) == 0 {
    		return nil
    	}
    
    	s.logger.Debug("Uninstall items", zap.Int("Items", len(s.Items)))
    
    	for _, c := range s.Items {
    		if err := s.UninstallItem(ctx, c); err != nil {
    			item := c.Value(s)
    			s.logger.Error("Failed to uninstall item", zap.String("Item", item.ID),
    				zap.String("Item", item.CollectionID), zap.Error(err),
    			)
    			return errors.WithDetailf(errors.Wrap(err, "failed to uninstall item"),
    				"Возникла ошибка при удалении элемента %s(%s)", item.ID, item.CollectionID)
    		}
    	}
    
    	return nil
    }
    
    func (s *Setup) CheckItems(ctx context.Context) error {
    	if len(s.Items) == 0 {
    		return nil
    	}
    
    	var errs []error
    	s.logger.Debug("Check items", zap.Int("Items", len(s.Items)))
    
    	for col, itms := range groupByCollection(s.Items) {
    		exists, err := s.getItems(ctx, col, itms)
    		if err != nil {
    			return err
    		}
    
    		for _, c := range itms {
    			item := c.Value(s)
    			if _, ok := exists[item.ID]; !ok {
    				errs = append(errs, errors.WithDetailf(errors.New("not found"),
    					"Не найден элемент %s(%s)", item.ID, item.CollectionID))
    			}
    		}
    	}
    
    	if len(errs) > 0 {
    		return errors.WithErrors(ErrCheckItems, errs...)
    	}
    
    	return nil
    }
    
    //
    //func (s *Setup) removeItems(collID string) {
    //	itms := make([]ItemConfig, 0, len(s.Items))
    //	for _, i := range s.Items {
    //		if i.item.CollectionID != collID {
    //			itms = append(itms, i)
    //		}
    //	}
    //	s.Items = itms
    //}
    
    func groupByCollection(itms Items) map[string]Items {
    	res := map[string]Items{}
    	for _, conf := range itms {
    		collectionID := conf.value.CollectionID
    		l, ok := res[collectionID]
    		if !ok {
    			res[collectionID] = make(Items, 0, 1)
    		}
    		res[collectionID] = append(l, conf)
    	}
    	return res
    }
    
    func (s *Setup) getItems(ctx context.Context, collID string, confs Items) (map[string]*items.Item, error) {
    	itms, _, err := s.content.Items.Find(
    		ctx,
    		s.SpaceID,
    		s.EnvironmentID,
    		collID,
    		&items.Filter{ID: confs.GetIDs()},
    		&items.FindOptions{Regular: true, Hidden: true, Templates: true},
    	)
    
    	if err != nil {
    		s.logger.Error("Failed to find existing items", zap.String("Collection", collID), zap.Error(err))
    		return nil, errors.WithDetailf(errors.Wrap(err, "failed to find existing items"),
    			"Возникла ошибка при получении элементов в коллекции %s", collID)
    	}
    
    	exists := make(map[string]*items.Item, len(itms))
    	for _, itm := range itms {
    		exists[itm.ID] = itm
    	}
    	return exists, nil
    }