package model import ( "fmt" "github.com/mattermost/focalboard/server/utils" "github.com/mattermost/mattermost-server/v6/shared/mlog" ) // GenerateBlockIDs generates new IDs for all the blocks of the list, // keeping consistent any references that other blocks would made to // the original IDs, so a tree of blocks can get new IDs and maintain // its shape. func GenerateBlockIDs(blocks []Block, logger mlog.LoggerIFace) []Block { blockIDs := map[string]BlockType{} referenceIDs := map[string]bool{} for _, block := range blocks { if _, ok := blockIDs[block.ID]; !ok { blockIDs[block.ID] = block.Type } if _, ok := referenceIDs[block.BoardID]; !ok { referenceIDs[block.BoardID] = true } if _, ok := referenceIDs[block.ParentID]; !ok { referenceIDs[block.ParentID] = true } if _, ok := block.Fields["contentOrder"]; ok { contentOrder, typeOk := block.Fields["contentOrder"].([]interface{}) if !typeOk { logger.Warn( "type assertion failed for content order when saving reference block IDs", mlog.String("blockID", block.ID), mlog.String("actionType", fmt.Sprintf("%T", block.Fields["contentOrder"])), mlog.String("expectedType", "[]interface{}"), mlog.String("contentOrder", fmt.Sprintf("%v", block.Fields["contentOrder"])), ) continue } for _, blockID := range contentOrder { switch v := blockID.(type) { case []interface{}: for _, columnBlockID := range v { referenceIDs[columnBlockID.(string)] = true } case string: referenceIDs[v] = true default: } } } } newIDs := map[string]string{} for id, blockType := range blockIDs { for referenceID := range referenceIDs { if id == referenceID { newIDs[id] = utils.NewID(BlockType2IDType(blockType)) continue } } } getExistingOrOldID := func(id string) string { if existingID, ok := newIDs[id]; ok { return existingID } return id } getExistingOrNewID := func(id string) string { if existingID, ok := newIDs[id]; ok { return existingID } return utils.NewID(BlockType2IDType(blockIDs[id])) } newBlocks := make([]Block, len(blocks)) for i, block := range blocks { block.ID = getExistingOrNewID(block.ID) block.BoardID = getExistingOrOldID(block.BoardID) block.ParentID = getExistingOrOldID(block.ParentID) blockMod := block if _, ok := blockMod.Fields["contentOrder"]; ok { fixFieldIDs(&blockMod, "contentOrder", getExistingOrOldID, logger) } if _, ok := blockMod.Fields["cardOrder"]; ok { fixFieldIDs(&blockMod, "cardOrder", getExistingOrOldID, logger) } newBlocks[i] = blockMod } return newBlocks } func fixFieldIDs(block *Block, fieldName string, getExistingOrOldID func(string) string, logger mlog.LoggerIFace) { field, typeOk := block.Fields[fieldName].([]interface{}) if !typeOk { logger.Warn( "type assertion failed for JSON field when setting new block IDs", mlog.String("blockID", block.ID), mlog.String("fieldName", fieldName), mlog.String("actionType", fmt.Sprintf("%T", block.Fields[fieldName])), mlog.String("expectedType", "[]interface{}"), mlog.String("value", fmt.Sprintf("%v", block.Fields[fieldName])), ) } else { for j := range field { switch v := field[j].(type) { case string: field[j] = getExistingOrOldID(v) case []interface{}: subOrder := field[j].([]interface{}) for k := range v { subOrder[k] = getExistingOrOldID(v[k].(string)) } } } } }