2020-10-16 19:12:53 +02:00
|
|
|
package model
|
|
|
|
|
2020-11-09 14:19:03 +02:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2022-01-06 16:41:18 +02:00
|
|
|
"fmt"
|
2020-11-09 14:19:03 +02:00
|
|
|
"io"
|
2021-11-05 12:54:27 +02:00
|
|
|
|
2022-01-06 16:41:18 +02:00
|
|
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
|
|
|
|
2021-11-05 12:54:27 +02:00
|
|
|
"github.com/mattermost/focalboard/server/utils"
|
2020-11-09 14:19:03 +02:00
|
|
|
)
|
|
|
|
|
2021-02-17 21:29:20 +02:00
|
|
|
// Block is the basic data unit
|
|
|
|
// swagger:model
|
2020-10-16 19:12:53 +02:00
|
|
|
type Block struct {
|
2021-02-17 21:29:20 +02:00
|
|
|
// The id for this block
|
|
|
|
// required: true
|
|
|
|
ID string `json:"id"`
|
|
|
|
|
|
|
|
// The id for this block's parent block. Empty for root blocks
|
|
|
|
// required: false
|
|
|
|
ParentID string `json:"parentId"`
|
|
|
|
|
|
|
|
// The id for this block's root block
|
|
|
|
// required: true
|
|
|
|
RootID string `json:"rootId"`
|
|
|
|
|
2021-07-08 16:36:43 +02:00
|
|
|
// The id for user who created this block
|
|
|
|
// required: true
|
|
|
|
CreatedBy string `json:"createdBy"`
|
|
|
|
|
2021-02-17 21:29:20 +02:00
|
|
|
// The id for user who last modified this block
|
|
|
|
// required: true
|
|
|
|
ModifiedBy string `json:"modifiedBy"`
|
|
|
|
|
|
|
|
// The schema version of this block
|
|
|
|
// required: true
|
|
|
|
Schema int64 `json:"schema"`
|
|
|
|
|
|
|
|
// The block type
|
|
|
|
// required: true
|
2021-10-27 03:11:19 +02:00
|
|
|
Type BlockType `json:"type"`
|
2021-02-17 21:29:20 +02:00
|
|
|
|
|
|
|
// The display title
|
|
|
|
// required: false
|
|
|
|
Title string `json:"title"`
|
|
|
|
|
|
|
|
// The block fields
|
|
|
|
// required: false
|
|
|
|
Fields map[string]interface{} `json:"fields"`
|
|
|
|
|
|
|
|
// The creation time
|
|
|
|
// required: true
|
|
|
|
CreateAt int64 `json:"createAt"`
|
|
|
|
|
|
|
|
// The last modified time
|
|
|
|
// required: true
|
|
|
|
UpdateAt int64 `json:"updateAt"`
|
|
|
|
|
|
|
|
// The deleted time. Set to indicate this block is deleted
|
|
|
|
// required: false
|
|
|
|
DeleteAt int64 `json:"deleteAt"`
|
2021-09-22 21:57:00 +02:00
|
|
|
|
|
|
|
// The workspace id that the block belongs to
|
|
|
|
// required: true
|
|
|
|
WorkspaceID string `json:"workspaceId"`
|
2020-10-16 19:12:53 +02:00
|
|
|
}
|
2020-11-09 14:19:03 +02:00
|
|
|
|
2021-08-06 14:10:24 +02:00
|
|
|
// BlockPatch is a patch for modify blocks
|
|
|
|
// swagger:model
|
|
|
|
type BlockPatch struct {
|
|
|
|
// The id for this block's parent block. Empty for root blocks
|
|
|
|
// required: false
|
|
|
|
ParentID *string `json:"parentId"`
|
|
|
|
|
|
|
|
// The id for this block's root block
|
|
|
|
// required: false
|
|
|
|
RootID *string `json:"rootId"`
|
|
|
|
|
|
|
|
// The schema version of this block
|
|
|
|
// required: false
|
|
|
|
Schema *int64 `json:"schema"`
|
|
|
|
|
|
|
|
// The block type
|
|
|
|
// required: false
|
2021-10-27 03:11:19 +02:00
|
|
|
Type *BlockType `json:"type"`
|
2021-08-06 14:10:24 +02:00
|
|
|
|
|
|
|
// The display title
|
|
|
|
// required: false
|
|
|
|
Title *string `json:"title"`
|
|
|
|
|
|
|
|
// The block updated fields
|
|
|
|
// required: false
|
|
|
|
UpdatedFields map[string]interface{} `json:"updatedFields"`
|
|
|
|
|
|
|
|
// The block removed fields
|
|
|
|
// required: false
|
|
|
|
DeletedFields []string `json:"deletedFields"`
|
|
|
|
}
|
|
|
|
|
2021-12-10 16:17:00 +02:00
|
|
|
// BlockPatchBatch is a batch of IDs and patches for modify blocks
|
|
|
|
// swagger:model
|
|
|
|
type BlockPatchBatch struct {
|
|
|
|
// The id's for of the blocks to patch
|
|
|
|
BlockIDs []string `json:"block_ids"`
|
|
|
|
|
|
|
|
// The BlockPatches to be applied
|
|
|
|
BlockPatches []BlockPatch `json:"block_patches"`
|
|
|
|
}
|
|
|
|
|
2022-02-01 20:36:12 +02:00
|
|
|
// BlockModifier is a callback that can modify each block during an import.
|
|
|
|
// A cache of arbitrary data will be passed for each call and any changes
|
|
|
|
// to the cache will be preserved for the next call.
|
|
|
|
// Return true to import the block or false to skip import.
|
|
|
|
type BlockModifier func(block *Block, cache map[string]interface{}) bool
|
|
|
|
|
2020-11-09 14:19:03 +02:00
|
|
|
func BlocksFromJSON(data io.Reader) []Block {
|
|
|
|
var blocks []Block
|
2021-06-21 11:21:42 +02:00
|
|
|
_ = json.NewDecoder(data).Decode(&blocks)
|
2020-11-09 14:19:03 +02:00
|
|
|
return blocks
|
|
|
|
}
|
2021-06-11 14:24:51 +02:00
|
|
|
|
|
|
|
// LogClone implements the `mlog.LogCloner` interface to provide a subset of Block fields for logging.
|
|
|
|
func (b Block) LogClone() interface{} {
|
|
|
|
return struct {
|
|
|
|
ID string
|
|
|
|
ParentID string
|
|
|
|
RootID string
|
2021-10-27 03:11:19 +02:00
|
|
|
Type BlockType
|
2021-06-11 14:24:51 +02:00
|
|
|
}{
|
|
|
|
ID: b.ID,
|
|
|
|
ParentID: b.ParentID,
|
|
|
|
RootID: b.RootID,
|
|
|
|
Type: b.Type,
|
|
|
|
}
|
|
|
|
}
|
2021-08-06 14:10:24 +02:00
|
|
|
|
|
|
|
// Patch returns an update version of the block.
|
|
|
|
func (p *BlockPatch) Patch(block *Block) *Block {
|
|
|
|
if p.ParentID != nil {
|
|
|
|
block.ParentID = *p.ParentID
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.RootID != nil {
|
|
|
|
block.RootID = *p.RootID
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.Schema != nil {
|
|
|
|
block.Schema = *p.Schema
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.Type != nil {
|
|
|
|
block.Type = *p.Type
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.Title != nil {
|
|
|
|
block.Title = *p.Title
|
|
|
|
}
|
|
|
|
|
|
|
|
for key, field := range p.UpdatedFields {
|
|
|
|
block.Fields[key] = field
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, key := range p.DeletedFields {
|
|
|
|
delete(block.Fields, key)
|
|
|
|
}
|
|
|
|
|
|
|
|
return block
|
|
|
|
}
|
2021-11-05 12:54:27 +02:00
|
|
|
|
2021-12-10 17:46:37 +02:00
|
|
|
// QuerySubtreeOptions are query options that can be passed to GetSubTree methods.
|
|
|
|
type QuerySubtreeOptions struct {
|
|
|
|
BeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt
|
|
|
|
AfterUpdateAt int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt
|
|
|
|
Limit uint64 // if non-zero then limit the number of returned records
|
|
|
|
}
|
|
|
|
|
|
|
|
// QueryBlockHistoryOptions are query options that can be passed to GetBlockHistory.
|
|
|
|
type QueryBlockHistoryOptions struct {
|
|
|
|
BeforeUpdateAt int64 // if non-zero then filter for records with update_at less than BeforeUpdateAt
|
|
|
|
AfterUpdateAt int64 // if non-zero then filter for records with update_at greater than AfterUpdateAt
|
|
|
|
Limit uint64 // if non-zero then limit the number of returned records
|
|
|
|
Descending bool // if true then the records are sorted by insert_at in descending order
|
|
|
|
}
|
|
|
|
|
2021-11-05 12:54:27 +02:00
|
|
|
// 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.
|
2022-01-06 16:41:18 +02:00
|
|
|
func GenerateBlockIDs(blocks []Block, logger *mlog.Logger) []Block {
|
2021-11-05 12:54:27 +02:00
|
|
|
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.RootID]; !ok {
|
|
|
|
referenceIDs[block.RootID] = true
|
|
|
|
}
|
|
|
|
if _, ok := referenceIDs[block.ParentID]; !ok {
|
|
|
|
referenceIDs[block.ParentID] = true
|
|
|
|
}
|
2022-01-06 16:41:18 +02:00
|
|
|
|
|
|
|
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 {
|
2022-01-11 22:25:51 +02:00
|
|
|
switch v := blockID.(type) {
|
|
|
|
case []interface{}:
|
|
|
|
for _, columnBlockID := range v {
|
|
|
|
referenceIDs[columnBlockID.(string)] = true
|
|
|
|
}
|
|
|
|
case string:
|
|
|
|
referenceIDs[v] = true
|
|
|
|
default:
|
|
|
|
}
|
2022-01-06 16:41:18 +02:00
|
|
|
}
|
|
|
|
}
|
2021-11-05 12:54:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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.RootID = getExistingOrOldID(block.RootID)
|
|
|
|
block.ParentID = getExistingOrOldID(block.ParentID)
|
|
|
|
|
2022-01-06 16:41:18 +02:00
|
|
|
if _, ok := block.Fields["contentOrder"]; ok {
|
|
|
|
contentOrder, typeOk := block.Fields["contentOrder"].([]interface{})
|
|
|
|
if !typeOk {
|
|
|
|
logger.Warn(
|
|
|
|
"type assertion failed for content order when setting new 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"])),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
for j := range contentOrder {
|
2022-01-11 22:25:51 +02:00
|
|
|
switch v := contentOrder[j].(type) {
|
|
|
|
case string:
|
|
|
|
contentOrder[j] = getExistingOrOldID(v)
|
|
|
|
case []interface{}:
|
|
|
|
subOrder := contentOrder[j].([]interface{})
|
|
|
|
for k := range v {
|
|
|
|
subOrder[k] = getExistingOrOldID(v[k].(string))
|
|
|
|
}
|
|
|
|
}
|
2022-01-06 16:41:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 12:54:27 +02:00
|
|
|
newBlocks[i] = block
|
|
|
|
}
|
|
|
|
|
|
|
|
return newBlocks
|
|
|
|
}
|