Select Git revision
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
}