~evanj/cms

023ce1019bb0b1c4a4d52a1cb25907977821160e — Evan M Jones 4 months ago 60e262c
Fix(SpaceCopy): Full space copy couldn't handle looping references. This
has been fixed.
2 files changed, 89 insertions(+), 39 deletions(-)

M internal/s/db/content.go
M internal/s/db/space.go
M internal/s/db/content.go => internal/s/db/content.go +27 -19
@@ 612,20 612,14 @@ func (db *DB) ContentRefererList(contentID string) (ret []ContentRefSet, err err
	return db.contentRefererList(contentID, defaultDepth)
}

func (db *DB) ContentUpdate(space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) (content.Content, error) {
func (db *DB) contentUpdate(t *sql.Tx, space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) error {
	depth := defaultDepth

	t, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	// TODO: Do we have to do this w/ types? Can you not find a better way?
	// I'm beginning to think we're over using interfaces and it's hurting.
	c, ok := content.(*Content)
	if !ok {
		return nil, fmt.Errorf("failed to type cast value type before attempting to update content")
		return fmt.Errorf("failed to type cast value type before attempting to update content")
	}

	for _, item := range updateParams {


@@ 637,7 631,7 @@ func (db *DB) ContentUpdate(space space.Space, ct contenttype.ContentType, conte
			}

			if err := db.valueReferenceListUpdate(space, ct, c, t, item.ID, strings.Split(item.Value, "-"), depth); err != nil {
				return nil, err
				return err
			}
			continue
		}


@@ 649,12 643,12 @@ func (db *DB) ContentUpdate(space space.Space, ct contenttype.ContentType, conte

		_, _, queryValueUpdate, err := db.valueQuerySetByType(item.Type)
		if err != nil {
			return nil, err
			return err
		}

		if _, err := t.Exec(queryValueUpdate, item.Value, item.ID); err != nil {
			db.log.Println(err)
			return nil, fmt.Errorf("failed to create update content value '%s'", item.Value)
			return fmt.Errorf("failed to create update content value '%s'", item.Value)
		}
	}



@@ 667,7 661,7 @@ func (db *DB) ContentUpdate(space space.Space, ct contenttype.ContentType, conte
			}

			if err := db.valueReferenceListNew(space, ct, c, t, item.Name, strings.Split(item.Value, "-"), depth); err != nil {
				return nil, err
				return err
			}
			continue
		}


@@ 679,41 673,55 @@ func (db *DB) ContentUpdate(space space.Space, ct contenttype.ContentType, conte

		queryValueNewType, queryValueGetTypeByID, _, err := db.valueQuerySetByType(item.Type)
		if err != nil {
			return nil, err
			return err
		}

		res, err := t.Exec(queryValueNewType, item.Value)
		if err != nil {
			return nil, fmt.Errorf("failed to attach field value of content")
			return fmt.Errorf("failed to attach field value of content")
		}

		valueID, err := res.LastInsertId()
		if err != nil {
			return nil, fmt.Errorf("failed to read new field value of content")
			return fmt.Errorf("failed to read new field value of content")
		}

		res, err = t.Exec(queryValueNew, content.ID(), ct.ID(), item.Name, valueID)
		if err != nil {
			return nil, fmt.Errorf("failed to attach field value of content")
			return fmt.Errorf("failed to attach field value of content")
		}

		valueID, err = res.LastInsertId()
		if err != nil {
			return nil, fmt.Errorf("failed to read new field value of content")
			return fmt.Errorf("failed to read new field value of content")
		}

		var value ContentValue
		if err := t.QueryRow(queryValueGetTypeByID, valueID).Scan(&value.FieldID, &value.FieldType, &value.FieldName, &value.FieldValue); err != nil {
			return nil, fmt.Errorf("failed to find value created")
			return fmt.Errorf("failed to find value created")
		}

		if err := db.contentValueAttachRef(t, &value, depth); err != nil {
			return nil, err
			return err
		}

		c.ContentValues = append(c.ContentValues, value)
	}

	return nil
}

func (db *DB) ContentUpdate(space space.Space, ct contenttype.ContentType, content content.Content, newParams []ContentNewParam, updateParams []ContentUpdateParam) (content.Content, error) {
	t, err := db.Begin()
	if err != nil {
		return nil, err
	}
	defer t.Rollback()

	if err := db.contentUpdate(t, space, ct, content, newParams, updateParams); err != nil {
		return nil, err
	}

	if err := t.Commit(); err != nil {
		return nil, err
	}

M internal/s/db/space.go => internal/s/db/space.go +62 -20
@@ 169,11 169,13 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, next, prevS space.Space) error {
		c  content.Content
	}

	// We've made a change to the content copy impl. We skip first pass of ref
	// types then update contents. This fixes loop references.

	// todo are contents that could not be processes yet, due to Reference and
	// ReferenceList dependencies. They will be taken care of in later iterations
	// (up to a depth of three).
	var todo []cct
	var todoN []cct // Used for later for loop.

	// refmap is used for todo to pull information from.
	// old content ID -> new content


@@ 223,26 225,17 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, next, prevS space.Space) error {
			}

			if !ok {
				// If we're not OK that means we need to update the content to account for ref types.
				todo = append(todo, cct{ct, prevC})
			}
		}
	}

	// NOTE: Going over defaultDepth should be an impossible case, as we stop
	// user from creating depths of >3 elsewhere. It's a hard limit.
	for i := 0; len(todo) > 0 && i < defaultDepth; i++ {
		todoN = append(todo[:0:0], todo...) // Copy todo to todoN.
		todo = nil

		for _, set := range todoN {
			ok, err := db.spaceCopyContent(t, next, set.ct, set.c, refmap)
			if err != nil {
				return err
			}

			if !ok {
				todo = append(todo, set)
			}
	// Update reference types that were skipped because content was not createt.
	for _, set := range todo {
		err := db.spaceUpdateContent(t, next, set.ct, set.c, refmap)
		if err != nil {
			return fmt.Errorf("failed to copy for references %s: %w", set.c.MustValueByName("name").Value(), err)
		}
	}



@@ 250,7 243,10 @@ func (db *DB) spaceCopyContentTypes(t *sql.Tx, next, prevS space.Space) error {
}

func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.ContentType, prevC content.Content, refmap map[string]content.Content) (bool, error) {
	var params []ContentNewParam
	var (
		params []ContentNewParam
		skip   bool
	)

	for _, prevF := range ct.Fields() {
		prevV, ok := prevC.ValueByName(prevF.Name())


@@ 272,7 268,8 @@ func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.Conte
		case valuetype.Reference:
			ref, ok := refmap[prevV.RefID()]
			if !ok {
				return false, nil
				skip = true
				continue
			}
			params = append(params, ContentNewParam{prevV.Type(), prevV.Name(), ref.ID()})



@@ 281,7 278,8 @@ func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.Conte
			for _, refID := range prevV.RefListIDs() {
				ref, ok := refmap[refID]
				if !ok {
					return false, nil
					skip = true
					continue
				}
				IDs = append(IDs, ref.ID())
			}


@@ 300,7 298,51 @@ func (db *DB) spaceCopyContent(t *sql.Tx, next space.Space, ct contenttype.Conte

	refmap[prevC.ID()] = c

	return true, nil
	return !skip, nil
}

func (db *DB) spaceUpdateContent(t *sql.Tx, next space.Space, ct contenttype.ContentType, prevC content.Content, refmap map[string]content.Content) error {
	var params []ContentNewParam

	for _, prevF := range ct.Fields() {
		prevV, ok := prevC.ValueByName(prevF.Name())
		if !ok {
			if prevF.Type() == valuetype.Reference || prevF.Type() == valuetype.ReferenceList {
				// Referenced content was deleted.
				continue
			}

			// "null" field. E.G. empty StringSmall.
			prevV = &ContentValue{
				FieldType: string(prevF.Type()),
				FieldName: prevF.Name(),
			}
		}

		switch prevF.Type() {

		case valuetype.Reference:
			ref, ok := refmap[prevV.RefID()]
			if !ok {
				return errors.New("failed to copy reference value: content not created")
			}
			params = append(params, ContentNewParam{prevV.Type(), prevV.Name(), ref.ID()})

		case valuetype.ReferenceList:
			var IDs []string
			for _, refID := range prevV.RefListIDs() {
				ref, ok := refmap[refID]
				if !ok {
					return errors.New("failed to copy reference list value(s): content not created")
				}
				IDs = append(IDs, ref.ID())
			}
			params = append(params, ContentNewParam{prevV.Type(), prevV.Name(), strings.Join(IDs, "-")})

		}
	}

	return db.contentUpdate(t, next, ct, refmap[prevC.ID()], params, []ContentUpdateParam{})
}

func (db *DB) spaceGet(t *sql.Tx, user user.User, spaceID string) (space.Space, error) {