Add rootId to blocks. Requires archive export / re-import.

This commit is contained in:
Chen-I Lim 2020-12-03 14:09:48 -08:00
parent b9b1020748
commit 850f60e1fb
23 changed files with 625 additions and 119 deletions

View File

@ -238,7 +238,7 @@ func (a *API) handleImport(w http.ResponseWriter, r *http.Request) {
err = a.app().InsertBlocks(blocks)
if err != nil {
log.Printf(`ERROR: %v`, r)
log.Printf(`ERROR: %v`, err)
errorResponse(w, http.StatusInternalServerError, nil)
return

View File

@ -9,6 +9,7 @@ import (
type Block struct {
ID string `json:"id"`
ParentID string `json:"parentId"`
RootID string `json:"rootId"`
Schema int64 `json:"schema"`
Type string `json:"type"`
Title string `json:"title"`

View File

@ -3,6 +3,7 @@ package sqlstore
import (
"database/sql"
"encoding/json"
"errors"
"log"
"time"
@ -23,7 +24,7 @@ func (s *SQLStore) latestsBlocksSubquery() sq.SelectBuilder {
func (s *SQLStore) GetBlocksWithParentAndType(parentID string, blockType string) ([]model.Block, error) {
query := s.getQueryBuilder().
Select("id", "parent_id", "schema", "type", "title",
Select("id", "parent_id", "root_id", "schema", "type", "title",
"COALESCE(\"fields\", '{}')", "create_at", "update_at",
"delete_at").
FromSelect(s.latestsBlocksSubquery(), "latest").
@ -42,7 +43,7 @@ func (s *SQLStore) GetBlocksWithParentAndType(parentID string, blockType string)
func (s *SQLStore) GetBlocksWithParent(parentID string) ([]model.Block, error) {
query := s.getQueryBuilder().
Select("id", "parent_id", "schema", "type", "title",
Select("id", "parent_id", "root_id", "schema", "type", "title",
"COALESCE(\"fields\", '{}')", "create_at", "update_at",
"delete_at").
FromSelect(s.latestsBlocksSubquery(), "latest").
@ -60,7 +61,7 @@ func (s *SQLStore) GetBlocksWithParent(parentID string) ([]model.Block, error) {
func (s *SQLStore) GetBlocksWithType(blockType string) ([]model.Block, error) {
query := s.getQueryBuilder().
Select("id", "parent_id", "schema", "type", "title",
Select("id", "parent_id", "root_id", "schema", "type", "title",
"COALESCE(\"fields\", '{}')", "create_at", "update_at",
"delete_at").
FromSelect(s.latestsBlocksSubquery(), "latest").
@ -79,7 +80,7 @@ func (s *SQLStore) GetBlocksWithType(blockType string) ([]model.Block, error) {
// GetSubTree2 returns blocks within 2 levels of the given blockID
func (s *SQLStore) GetSubTree2(blockID string) ([]model.Block, error) {
query := s.getQueryBuilder().
Select("id", "parent_id", "schema", "type", "title",
Select("id", "parent_id", "root_id", "schema", "type", "title",
"COALESCE(\"fields\", '{}')", "create_at", "update_at",
"delete_at").
FromSelect(s.latestsBlocksSubquery(), "latest").
@ -98,7 +99,7 @@ func (s *SQLStore) GetSubTree2(blockID string) ([]model.Block, error) {
// GetSubTree3 returns blocks within 3 levels of the given blockID
func (s *SQLStore) GetSubTree3(blockID string) ([]model.Block, error) {
// This first subquery returns repeated blocks
subquery1 := sq.Select("l3.id", "l3.parent_id", "l3.schema", "l3.type", "l3.title",
subquery1 := sq.Select("l3.id", "l3.parent_id", "l3.root_id", "l3.schema", "l3.type", "l3.title",
"l3.fields", "l3.create_at", "l3.update_at",
"l3.delete_at").
FromSelect(s.latestsBlocksSubquery(), "l1").
@ -111,7 +112,7 @@ func (s *SQLStore) GetSubTree3(blockID string) ([]model.Block, error) {
subquery2 := sq.Select("*", "ROW_NUMBER() OVER (PARTITION BY id) AS rn").
FromSelect(subquery1, "sub1")
query := s.getQueryBuilder().Select("id", "parent_id", "schema", "type", "title",
query := s.getQueryBuilder().Select("id", "parent_id", "root_id", "schema", "type", "title",
"COALESCE(\"fields\", '{}')", "create_at", "update_at",
"delete_at").
FromSelect(subquery2, "sub2").
@ -129,7 +130,7 @@ func (s *SQLStore) GetSubTree3(blockID string) ([]model.Block, error) {
func (s *SQLStore) GetAllBlocks() ([]model.Block, error) {
query := s.getQueryBuilder().
Select("id", "parent_id", "schema", "type", "title",
Select("id", "parent_id", "root_id", "schema", "type", "title",
"COALESCE(\"fields\", '{}')", "create_at", "update_at",
"delete_at").
FromSelect(s.latestsBlocksSubquery(), "latest")
@ -156,6 +157,7 @@ func blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
err := rows.Scan(
&block.ID,
&block.ParentID,
&block.RootID,
&block.Schema,
&block.Type,
&block.Title,
@ -202,14 +204,18 @@ func (s *SQLStore) GetParentID(blockID string) (string, error) {
}
func (s *SQLStore) InsertBlock(block model.Block) error {
if block.RootID == "" {
return errors.New("rootId is nil")
}
fieldsJSON, err := json.Marshal(block.Fields)
if err != nil {
return err
}
query := s.getQueryBuilder().Insert("blocks").
Columns("id", "parent_id", "schema", "type", "title", "fields", "create_at", "update_at", "delete_at").
Values(block.ID, block.ParentID, block.Schema, block.Type, block.Title,
Columns("id", "parent_id", "root_id", "schema", "type", "title", "fields", "create_at", "update_at", "delete_at").
Values(block.ID, block.ParentID, block.RootID, block.Schema, block.Type, block.Title,
fieldsJSON, block.CreateAt, block.UpdateAt, block.DeleteAt)
_, err = query.Exec()

View File

@ -1,3 +1,11 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// postgres_files/000001_init.down.sql
// postgres_files/000001_init.up.sql
// postgres_files/000002_system_settings_table.down.sql
// postgres_files/000002_system_settings_table.up.sql
// postgres_files/000003_blocks_rootid.down.sql
// postgres_files/000003_blocks_rootid.up.sql
package postgres
import (
@ -5,10 +13,14 @@ import (
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindata_read(data []byte, name string) ([]byte, error) {
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
@ -16,62 +28,210 @@ func bindata_read(data []byte, name string) ([]byte, error) {
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
gz.Close()
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
var __000001_init_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xb6\xe6\x02\x04\x00\x00\xff\xff\x45\xbe\x01\x0f\x13\x00\x00\x00")
type asset struct {
bytes []byte
info os.FileInfo
}
func _000001_init_down_sql() ([]byte, error) {
return bindata_read(
__000001_init_down_sql,
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var __000001_initDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xb6\xe6\x02\x04\x00\x00\xff\xff\x45\xbe\x01\x0f\x13\x00\x00\x00")
func _000001_initDownSqlBytes() ([]byte, error) {
return bindataRead(
__000001_initDownSql,
"000001_init.down.sql",
)
}
var __000001_init_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x8f\x31\x4f\xc3\x30\x10\x85\xe7\xf8\x57\xbc\x31\x91\xb2\x21\xb1\x30\xb9\xe5\x0a\x86\xc4\xa9\x9c\x2b\xb4\x2c\x55\x88\x0f\x61\x11\x4a\x14\x9b\x81\x7f\x8f\xda\x21\x43\xba\xdd\x7d\xba\xef\x9e\xde\xda\x91\x66\x02\xeb\x55\x45\x30\x1b\xd8\x86\x41\x7b\xd3\x72\x8b\xf7\xe1\xa7\xff\x8a\xc8\x55\x16\x3c\x5e\xb4\x5b\x3f\x6a\x97\xdf\xdc\x16\xa5\xca\xc2\x29\xca\x94\x8e\x5d\x02\x9b\x9a\x5a\xd6\xf5\x96\xdf\x2e\xae\xdd\x55\x15\xee\x69\xa3\x77\x15\xc3\x36\xaf\xf9\xf9\x7c\xec\x26\x39\xa5\xe3\xd5\x9b\xd8\x7f\xca\x77\x87\x95\x79\x30\x96\x4b\x95\xa5\xbf\x51\xc0\xb4\xbf\xcc\x21\x0d\xf3\xf2\x11\x64\xf0\x11\x4f\x6d\x63\x4b\x95\xf5\x93\x74\x49\xce\xe9\xb3\xf9\x3b\xfa\x25\xf2\x32\xc8\x02\x6d\x9d\xa9\xb5\x3b\xe0\x99\x0e\xc8\x83\x2f\x31\xf7\x28\x54\x71\xa7\xfe\x03\x00\x00\xff\xff\xa3\xc9\xa2\x70\x0c\x01\x00\x00")
func _000001_initDownSql() (*asset, error) {
bytes, err := _000001_initDownSqlBytes()
if err != nil {
return nil, err
}
func _000001_init_up_sql() ([]byte, error) {
return bindata_read(
__000001_init_up_sql,
info := bindataFileInfo{name: "000001_init.down.sql", size: 19, mode: os.FileMode(420), modTime: time.Unix(1603074564, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000001_initUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x8f\x31\x4f\xc3\x30\x10\x85\xe7\xf8\x57\xbc\x31\x91\xb2\x21\xb1\x30\xb9\xe5\x0a\x86\xc4\xa9\x9c\x2b\xb4\x2c\x55\x88\x0f\x61\x11\x4a\x14\x9b\x81\x7f\x8f\xda\x21\x43\xba\xdd\x7d\xba\xef\x9e\xde\xda\x91\x66\x02\xeb\x55\x45\x30\x1b\xd8\x86\x41\x7b\xd3\x72\x8b\xf7\xe1\xa7\xff\x8a\xc8\x55\x16\x3c\x5e\xb4\x5b\x3f\x6a\x97\xdf\xdc\x16\xa5\xca\xc2\x29\xca\x94\x8e\x5d\x02\x9b\x9a\x5a\xd6\xf5\x96\xdf\x2e\xae\xdd\x55\x15\xee\x69\xa3\x77\x15\xc3\x36\xaf\xf9\xf9\x7c\xec\x26\x39\xa5\xe3\xd5\x9b\xd8\x7f\xca\x77\x87\x95\x79\x30\x96\x4b\x95\xa5\xbf\x51\xc0\xb4\xbf\xcc\x21\x0d\xf3\xf2\x11\x64\xf0\x11\x4f\x6d\x63\x4b\x95\xf5\x93\x74\x49\xce\xe9\xb3\xf9\x3b\xfa\x25\xf2\x32\xc8\x02\x6d\x9d\xa9\xb5\x3b\xe0\x99\x0e\xc8\x83\x2f\x31\xf7\x28\x54\x71\xa7\xfe\x03\x00\x00\xff\xff\xa3\xc9\xa2\x70\x0c\x01\x00\x00")
func _000001_initUpSqlBytes() ([]byte, error) {
return bindataRead(
__000001_initUpSql,
"000001_init.up.sql",
)
}
var __000002_system_settings_table_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xb6\xe6\x02\x04\x00\x00\xff\xff\x8b\x60\xbf\x1e\x1c\x00\x00\x00")
func _000001_initUpSql() (*asset, error) {
bytes, err := _000001_initUpSqlBytes()
if err != nil {
return nil, err
}
func _000002_system_settings_table_down_sql() ([]byte, error) {
return bindata_read(
__000002_system_settings_table_down_sql,
info := bindataFileInfo{name: "000001_init.up.sql", size: 268, mode: os.FileMode(420), modTime: time.Unix(1607029670, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000002_system_settings_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xb6\xe6\x02\x04\x00\x00\xff\xff\x8b\x60\xbf\x1e\x1c\x00\x00\x00")
func _000002_system_settings_tableDownSqlBytes() ([]byte, error) {
return bindataRead(
__000002_system_settings_tableDownSql,
"000002_system_settings_table.down.sql",
)
}
var __000002_system_settings_table_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\x08\x71\x74\xf2\x71\x55\xf0\x74\x53\xf0\xf3\x0f\x51\x70\x8d\xf0\x0c\x0e\x09\x56\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\x56\xd0\xe0\xe2\xcc\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x34\x30\xd0\xd4\xe1\xe2\x2c\x4b\xcc\x29\x4d\x55\x08\x71\x8d\x08\xd1\xe1\xe2\x0c\x08\xf2\xf4\x75\x0c\x8a\x54\xf0\x76\x8d\x54\xd0\xc8\x4c\xd1\xe4\xd2\xb4\xe6\x02\x04\x00\x00\xff\xff\x17\x95\xca\x5b\x61\x00\x00\x00")
func _000002_system_settings_tableDownSql() (*asset, error) {
bytes, err := _000002_system_settings_tableDownSqlBytes()
if err != nil {
return nil, err
}
func _000002_system_settings_table_up_sql() ([]byte, error) {
return bindata_read(
__000002_system_settings_table_up_sql,
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 28, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000002_system_settings_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\x08\x71\x74\xf2\x71\x55\xf0\x74\x53\xf0\xf3\x0f\x51\x70\x8d\xf0\x0c\x0e\x09\x56\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\x56\xd0\xe0\xe2\xcc\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x34\x30\xd0\xd4\xe1\xe2\x2c\x4b\xcc\x29\x4d\x55\x08\x71\x8d\x08\xd1\xe1\xe2\x0c\x08\xf2\xf4\x75\x0c\x8a\x54\xf0\x76\x8d\x54\xd0\xc8\x4c\xd1\xe4\xd2\xb4\xe6\x02\x04\x00\x00\xff\xff\x17\x95\xca\x5b\x61\x00\x00\x00")
func _000002_system_settings_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
__000002_system_settings_tableUpSql,
"000002_system_settings_table.up.sql",
)
}
func _000002_system_settings_tableUpSql() (*asset, error) {
bytes, err := _000002_system_settings_tableUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 97, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000003_blocks_rootidDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\xb1\xe6\x02\x04\x00\x00\xff\xff\x94\x1c\x55\xb9\x28\x00\x00\x00")
func _000003_blocks_rootidDownSqlBytes() ([]byte, error) {
return bindataRead(
__000003_blocks_rootidDownSql,
"000003_blocks_rootid.down.sql",
)
}
func _000003_blocks_rootidDownSql() (*asset, error) {
bytes, err := _000003_blocks_rootidDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 40, mode: os.FileMode(420), modTime: time.Unix(1607029815, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000003_blocks_rootidUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\x02\x04\x00\x00\xff\xff\xce\x60\x70\x4e\x33\x00\x00\x00")
func _000003_blocks_rootidUpSqlBytes() ([]byte, error) {
return bindataRead(
__000003_blocks_rootidUpSql,
"000003_blocks_rootid.up.sql",
)
}
func _000003_blocks_rootidUpSql() (*asset, error) {
bytes, err := _000003_blocks_rootidUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 51, mode: os.FileMode(420), modTime: time.Unix(1607029769, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
return f()
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
@ -82,12 +242,15 @@ func AssetNames() []string {
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() ([]byte, error){
"000001_init.down.sql": _000001_init_down_sql,
"000001_init.up.sql": _000001_init_up_sql,
"000002_system_settings_table.down.sql": _000002_system_settings_table_down_sql,
"000002_system_settings_table.up.sql": _000002_system_settings_table_up_sql,
var _bindata = map[string]func() (*asset, error){
"000001_init.down.sql": _000001_initDownSql,
"000001_init.up.sql": _000001_initUpSql,
"000002_system_settings_table.down.sql": _000002_system_settings_tableDownSql,
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -117,23 +280,69 @@ func AssetDir(name string) ([]string, error) {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for name := range node.Children {
rv = append(rv, name)
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type _bintree_t struct {
Func func() ([]byte, error)
Children map[string]*_bintree_t
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"000001_init.down.sql": &_bintree_t{_000001_init_down_sql, map[string]*_bintree_t{
}},
"000001_init.up.sql": &_bintree_t{_000001_init_up_sql, map[string]*_bintree_t{
}},
"000002_system_settings_table.down.sql": &_bintree_t{_000002_system_settings_table_down_sql, map[string]*_bintree_t{
}},
"000002_system_settings_table.up.sql": &_bintree_t{_000002_system_settings_table_up_sql, map[string]*_bintree_t{
}},
var _bintree = &bintree{nil, map[string]*bintree{
"000001_init.down.sql": &bintree{_000001_initDownSql, map[string]*bintree{}},
"000001_init.up.sql": &bintree{_000001_initUpSql, map[string]*bintree{}},
"000002_system_settings_table.down.sql": &bintree{_000002_system_settings_tableDownSql, map[string]*bintree{}},
"000002_system_settings_table.up.sql": &bintree{_000002_system_settings_tableUpSql, map[string]*bintree{}},
"000003_blocks_rootid.down.sql": &bintree{_000003_blocks_rootidDownSql, map[string]*bintree{}},
"000003_blocks_rootid.up.sql": &bintree{_000003_blocks_rootidUpSql, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -0,0 +1,2 @@
ALTER TABLE blocks
DROP COLUMN root_id;

View File

@ -0,0 +1,2 @@
ALTER TABLE blocks
ADD COLUMN root_id VARCHAR(36);

View File

@ -1,3 +1,11 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// sqlite_files/000001_init.down.sql
// sqlite_files/000001_init.up.sql
// sqlite_files/000002_system_settings_table.down.sql
// sqlite_files/000002_system_settings_table.up.sql
// sqlite_files/000003_blocks_rootid.down.sql
// sqlite_files/000003_blocks_rootid.up.sql
package sqlite
import (
@ -5,10 +13,14 @@ import (
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindata_read(data []byte, name string) ([]byte, error) {
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
@ -16,62 +28,210 @@ func bindata_read(data []byte, name string) ([]byte, error) {
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
gz.Close()
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
var __000001_init_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xb6\xe6\x02\x04\x00\x00\xff\xff\x45\xbe\x01\x0f\x13\x00\x00\x00")
type asset struct {
bytes []byte
info os.FileInfo
}
func _000001_init_down_sql() ([]byte, error) {
return bindata_read(
__000001_init_down_sql,
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var __000001_initDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xb6\xe6\x02\x04\x00\x00\xff\xff\x45\xbe\x01\x0f\x13\x00\x00\x00")
func _000001_initDownSqlBytes() ([]byte, error) {
return bindataRead(
__000001_initDownSql,
"000001_init.down.sql",
)
}
var __000001_init_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x8f\xbf\x6e\x83\x30\x10\x87\x67\xfc\x14\xb7\x58\x18\x89\x4c\x95\x3a\xa4\x93\x93\x1c\x8d\x55\x20\x95\xb9\xb4\x61\x8a\x28\xbe\xa8\x56\x49\x8a\xc0\x1d\xfa\xf6\x15\x91\xca\x90\x6c\xf7\xfb\x4e\xdf\xfd\x59\x5b\xd4\x84\x40\x7a\x95\x23\x98\x0c\xca\x1d\x01\x1e\x4c\x45\x15\x7c\x74\xdf\xed\xd7\x08\x4a\x44\xde\xc1\x9b\xb6\xeb\xad\xb6\xea\xe1\x31\x49\x45\xe4\x2f\x23\x0f\xe1\xd8\x04\xd8\x68\x42\x32\x05\x5e\xc5\x72\x9f\xe7\xb0\xc1\x4c\xef\x73\x52\x15\xd9\x6c\xea\xa8\x58\xd6\x0b\x79\x5e\x48\x07\x72\xbb\x94\xc5\x52\x9e\xe2\x14\xe2\x72\xf7\x1e\x27\xd3\xac\xbe\x19\xf8\x12\x8e\x77\x3b\xc6\xf6\x93\xcf\x0d\xac\xcc\xb3\x29\x29\x15\x51\xf8\xed\x19\x08\x0f\xd7\xda\x87\x6e\x0e\x27\xcf\x9d\x1b\xff\x53\x3b\x70\x13\x78\x3a\x6d\x36\x7f\x7a\x77\x8b\x1c\x77\x7c\x83\x5e\xad\x29\xb4\xad\xe1\x05\x6b\x50\xde\xa5\x30\x3f\x99\x88\xe4\x49\xfc\x05\x00\x00\xff\xff\xa2\x33\x30\x8e\x29\x01\x00\x00")
func _000001_initDownSql() (*asset, error) {
bytes, err := _000001_initDownSqlBytes()
if err != nil {
return nil, err
}
func _000001_init_up_sql() ([]byte, error) {
return bindata_read(
__000001_init_up_sql,
info := bindataFileInfo{name: "000001_init.down.sql", size: 19, mode: os.FileMode(420), modTime: time.Unix(1603074564, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000001_initUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x64\x8f\xbf\x6e\x83\x30\x10\x87\x67\xfc\x14\xb7\x58\x18\x89\x4c\x95\x3a\xa4\x93\x93\x1c\x8d\x55\x20\x95\xb9\xb4\x61\x8a\x28\xbe\xa8\x56\x49\x8a\xc0\x1d\xfa\xf6\x15\x91\xca\x90\x6c\xf7\xfb\x4e\xdf\xfd\x59\x5b\xd4\x84\x40\x7a\x95\x23\x98\x0c\xca\x1d\x01\x1e\x4c\x45\x15\x7c\x74\xdf\xed\xd7\x08\x4a\x44\xde\xc1\x9b\xb6\xeb\xad\xb6\xea\xe1\x31\x49\x45\xe4\x2f\x23\x0f\xe1\xd8\x04\xd8\x68\x42\x32\x05\x5e\xc5\x72\x9f\xe7\xb0\xc1\x4c\xef\x73\x52\x15\xd9\x6c\xea\xa8\x58\xd6\x0b\x79\x5e\x48\x07\x72\xbb\x94\xc5\x52\x9e\xe2\x14\xe2\x72\xf7\x1e\x27\xd3\xac\xbe\x19\xf8\x12\x8e\x77\x3b\xc6\xf6\x93\xcf\x0d\xac\xcc\xb3\x29\x29\x15\x51\xf8\xed\x19\x08\x0f\xd7\xda\x87\x6e\x0e\x27\xcf\x9d\x1b\xff\x53\x3b\x70\x13\x78\x3a\x6d\x36\x7f\x7a\x77\x8b\x1c\x77\x7c\x83\x5e\xad\x29\xb4\xad\xe1\x05\x6b\x50\xde\xa5\x30\x3f\x99\x88\xe4\x49\xfc\x05\x00\x00\xff\xff\xa2\x33\x30\x8e\x29\x01\x00\x00")
func _000001_initUpSqlBytes() ([]byte, error) {
return bindataRead(
__000001_initUpSql,
"000001_init.up.sql",
)
}
var __000002_system_settings_table_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xb6\xe6\x02\x04\x00\x00\xff\xff\x8b\x60\xbf\x1e\x1c\x00\x00\x00")
func _000001_initUpSql() (*asset, error) {
bytes, err := _000001_initUpSqlBytes()
if err != nil {
return nil, err
}
func _000002_system_settings_table_down_sql() ([]byte, error) {
return bindata_read(
__000002_system_settings_table_down_sql,
info := bindataFileInfo{name: "000001_init.up.sql", size: 297, mode: os.FileMode(420), modTime: time.Unix(1607029839, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000002_system_settings_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xb6\xe6\x02\x04\x00\x00\xff\xff\x8b\x60\xbf\x1e\x1c\x00\x00\x00")
func _000002_system_settings_tableDownSqlBytes() ([]byte, error) {
return bindataRead(
__000002_system_settings_tableDownSql,
"000002_system_settings_table.down.sql",
)
}
var __000002_system_settings_table_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\x08\x71\x74\xf2\x71\x55\xf0\x74\x53\xf0\xf3\x0f\x51\x70\x8d\xf0\x0c\x0e\x09\x56\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xd6\xe0\xe2\xcc\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x34\x30\xd0\xd4\xe1\xe2\x2c\x4b\xcc\x29\x4d\x55\x08\x71\x8d\x08\xd1\xe1\xe2\x0c\x08\xf2\xf4\x75\x0c\x8a\x54\xf0\x76\x8d\x54\xd0\xc8\x4c\xd1\xe4\xd2\xb4\xe6\x02\x04\x00\x00\xff\xff\x1e\xfb\x02\xf2\x60\x00\x00\x00")
func _000002_system_settings_tableDownSql() (*asset, error) {
bytes, err := _000002_system_settings_tableDownSqlBytes()
if err != nil {
return nil, err
}
func _000002_system_settings_table_up_sql() ([]byte, error) {
return bindata_read(
__000002_system_settings_table_up_sql,
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 28, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000002_system_settings_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x0e\x72\x75\x0c\x71\x55\x08\x71\x74\xf2\x71\x55\xf0\x74\x53\xf0\xf3\x0f\x51\x70\x8d\xf0\x0c\x0e\x09\x56\x28\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xd6\xe0\xe2\xcc\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x34\x30\xd0\xd4\xe1\xe2\x2c\x4b\xcc\x29\x4d\x55\x08\x71\x8d\x08\xd1\xe1\xe2\x0c\x08\xf2\xf4\x75\x0c\x8a\x54\xf0\x76\x8d\x54\xd0\xc8\x4c\xd1\xe4\xd2\xb4\xe6\x02\x04\x00\x00\xff\xff\x1e\xfb\x02\xf2\x60\x00\x00\x00")
func _000002_system_settings_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
__000002_system_settings_tableUpSql,
"000002_system_settings_table.up.sql",
)
}
func _000002_system_settings_tableUpSql() (*asset, error) {
bytes, err := _000002_system_settings_tableUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 96, mode: os.FileMode(420), modTime: time.Unix(1603229117, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000003_blocks_rootidDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\xb1\xe6\x02\x04\x00\x00\xff\xff\x94\x1c\x55\xb9\x28\x00\x00\x00")
func _000003_blocks_rootidDownSqlBytes() ([]byte, error) {
return bindataRead(
__000003_blocks_rootidDownSql,
"000003_blocks_rootid.down.sql",
)
}
func _000003_blocks_rootidDownSql() (*asset, error) {
bytes, err := _000003_blocks_rootidDownSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 40, mode: os.FileMode(420), modTime: time.Unix(1607029815, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var __000003_blocks_rootidUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\x48\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\x02\x04\x00\x00\xff\xff\xce\x60\x70\x4e\x33\x00\x00\x00")
func _000003_blocks_rootidUpSqlBytes() ([]byte, error) {
return bindataRead(
__000003_blocks_rootidUpSql,
"000003_blocks_rootid.up.sql",
)
}
func _000003_blocks_rootidUpSql() (*asset, error) {
bytes, err := _000003_blocks_rootidUpSqlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 51, mode: os.FileMode(420), modTime: time.Unix(1607029769, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
return f()
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
@ -82,12 +242,15 @@ func AssetNames() []string {
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() ([]byte, error){
"000001_init.down.sql": _000001_init_down_sql,
"000001_init.up.sql": _000001_init_up_sql,
"000002_system_settings_table.down.sql": _000002_system_settings_table_down_sql,
"000002_system_settings_table.up.sql": _000002_system_settings_table_up_sql,
var _bindata = map[string]func() (*asset, error){
"000001_init.down.sql": _000001_initDownSql,
"000001_init.up.sql": _000001_initUpSql,
"000002_system_settings_table.down.sql": _000002_system_settings_tableDownSql,
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
@ -117,23 +280,69 @@ func AssetDir(name string) ([]string, error) {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for name := range node.Children {
rv = append(rv, name)
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type _bintree_t struct {
Func func() ([]byte, error)
Children map[string]*_bintree_t
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"000001_init.down.sql": &_bintree_t{_000001_init_down_sql, map[string]*_bintree_t{
}},
"000001_init.up.sql": &_bintree_t{_000001_init_up_sql, map[string]*_bintree_t{
}},
"000002_system_settings_table.down.sql": &_bintree_t{_000002_system_settings_table_down_sql, map[string]*_bintree_t{
}},
"000002_system_settings_table.up.sql": &_bintree_t{_000002_system_settings_table_up_sql, map[string]*_bintree_t{
}},
var _bintree = &bintree{nil, map[string]*bintree{
"000001_init.down.sql": &bintree{_000001_initDownSql, map[string]*bintree{}},
"000001_init.up.sql": &bintree{_000001_initUpSql, map[string]*bintree{}},
"000002_system_settings_table.down.sql": &bintree{_000002_system_settings_tableDownSql, map[string]*bintree{}},
"000002_system_settings_table.up.sql": &bintree{_000002_system_settings_tableUpSql, map[string]*bintree{}},
"000003_blocks_rootid.down.sql": &bintree{_000003_blocks_rootidDownSql, map[string]*bintree{}},
"000003_blocks_rootid.up.sql": &bintree{_000003_blocks_rootidUpSql, map[string]*bintree{}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -0,0 +1,2 @@
ALTER TABLE blocks
DROP COLUMN root_id;

View File

@ -0,0 +1,2 @@
ALTER TABLE blocks
ADD COLUMN root_id VARCHAR(36);

View File

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import {BoardTree} from './viewModel/boardTree'
import mutator from './mutator'
import {IBlock} from './blocks/block'
import {IBlock, IMutableBlock} from './blocks/block'
import {Utils} from './utils'
interface Archive {
@ -67,14 +67,15 @@ class Archiver {
Utils.log(`Import archive, version: ${archive.version}, date/time: ${date.toLocaleString()}, ${blocks.length} block(s).`)
// Basic error checking
const filteredBlocks = blocks.filter((o) => {
if (!o.id) {
return false
}
return true
})
let filteredBlocks = blocks.filter((o) => Boolean(o.id))
Utils.log(`Import ${filteredBlocks.length} filtered blocks.`)
Utils.log(`Import ${filteredBlocks.length} filtered blocks with ids.`)
this.fixRootIds(filteredBlocks)
filteredBlocks = filteredBlocks.filter((o) => Boolean(o.rootId))
Utils.log(`Import ${filteredBlocks.length} filtered blocks with rootIds.`)
await mutator.importFullArchive(filteredBlocks)
Utils.log('Import completed')
@ -87,6 +88,42 @@ class Archiver {
// TODO: Remove or reuse input
}
private static fixRootIds(blocks: IMutableBlock[]) {
const blockMap = new Map(blocks.map((o) => [o.id, o]))
const maxLevels = 5
for (let i = 0; i < maxLevels; i++) {
let missingRootIds = false
blocks.forEach((o) => {
if (o.parentId) {
const parent = blockMap.get(o.parentId)
if (parent) {
o.rootId = parent.rootId
} else {
Utils.assert(`No parent for ${o.type}: ${o.id} (${o.title})`)
}
if (!o.rootId) {
missingRootIds = true
}
} else {
o.rootId = o.id
}
})
if (!missingRootIds) {
Utils.log(`fixRootIds in ${i} levels`)
break
}
}
// Check and log remaining errors
blocks.forEach((o) => {
if (!o.rootId) {
const parent = blockMap.get(o.parentId)
Utils.logError(`RootId is null: ${o.type} ${o.id}, parentId ${o.parentId}: ${o.title}, parent: ${parent?.type}, parent.rootId: ${parent?.rootId}, parent.title: ${parent?.title}`)
}
})
}
}
export {Archiver}

View File

@ -7,6 +7,7 @@ type BlockTypes = 'board' | 'view' | 'card' | 'text' | 'image' | 'divider' | 'co
interface IBlock {
readonly id: string
readonly parentId: string
readonly rootId: string
readonly schema: number
readonly type: BlockTypes
@ -21,6 +22,7 @@ interface IBlock {
interface IMutableBlock extends IBlock {
id: string
parentId: string
rootId: string
schema: number
type: BlockTypes
@ -36,6 +38,7 @@ class MutableBlock implements IMutableBlock {
id: string = Utils.createGuid()
schema: number
parentId: string
rootId: string
type: BlockTypes
title: string
fields: Record<string, any> = {}
@ -59,6 +62,7 @@ class MutableBlock implements IMutableBlock {
this.id = block.id || Utils.createGuid()
this.schema = 1
this.parentId = block.parentId || ''
this.rootId = block.rootId || ''
this.type = block.type || ''
// Shallow copy here. Derived classes must make deep copies of their known properties in their constructors.

View File

@ -102,9 +102,9 @@ class MutableBoard extends MutableBlock {
}
duplicate(): MutableBoard {
const card = new MutableBoard(this)
card.id = Utils.createGuid()
return card
const board = new MutableBoard(this)
board.id = Utils.createGuid()
return board
}
}

View File

@ -497,6 +497,7 @@ class BoardComponent extends React.Component<Props, State> {
const card = new MutableCard()
card.parentId = boardTree.board.id
card.rootId = boardTree.board.rootId
const propertiesThatMeetFilters = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
if (boardTree.groupByProperty) {
if (groupByOptionId) {
@ -527,6 +528,7 @@ class BoardComponent extends React.Component<Props, State> {
const cardTemplate = new MutableCard()
cardTemplate.isTemplate = true
cardTemplate.parentId = boardTree.board.id
cardTemplate.rootId = boardTree.board.rootId
await mutator.insertBlock(
cardTemplate,
'add card template',

View File

@ -72,7 +72,7 @@ class CardDetail extends React.Component<Props, State> {
<ContentBlock
key={block.id}
block={block}
cardId={card.id}
card={card}
contents={cardTree.contents}
/>
))}
@ -88,6 +88,7 @@ class CardDetail extends React.Component<Props, State> {
if (text) {
const block = new MutableTextBlock()
block.parentId = card.id
block.rootId = card.rootId
block.title = text
block.order = (this.props.cardTree.contents.length + 1) * 1000
mutator.insertBlock(block, 'add card text')
@ -215,6 +216,7 @@ class CardDetail extends React.Component<Props, State> {
onClick={() => {
const block = new MutableTextBlock()
block.parentId = card.id
block.rootId = card.rootId
block.order = (this.props.cardTree.contents.length + 1) * 1000
mutator.insertBlock(block, 'add text')
}}
@ -223,7 +225,7 @@ class CardDetail extends React.Component<Props, State> {
id='image'
name={intl.formatMessage({id: 'CardDetail.image', defaultMessage: 'Image'})}
onClick={() => Utils.selectLocalFile(
(file) => mutator.createImageBlock(card.id, file, (this.props.cardTree.contents.length + 1) * 1000),
(file) => mutator.createImageBlock(card, file, (this.props.cardTree.contents.length + 1) * 1000),
'.jpg,.jpeg,.png',
)}
/>

View File

@ -3,6 +3,7 @@
import React from 'react'
import {IBlock} from '../blocks/block'
import {MutableDividerBlock} from '../blocks/dividerBlock'
import {IOrderedBlock} from '../blocks/orderedBlock'
import {MutableTextBlock} from '../blocks/textBlock'
@ -26,13 +27,13 @@ import {MarkdownEditor} from './markdownEditor'
type Props = {
block: IOrderedBlock
cardId: string
card: IBlock
contents: readonly IOrderedBlock[]
}
class ContentBlock extends React.PureComponent<Props> {
public render(): JSX.Element | null {
const {cardId, contents, block} = this.props
const {card, contents, block} = this.props
if (block.type !== 'text' && block.type !== 'image' && block.type !== 'divider') {
Utils.assertFailure(`Block type is unknown: ${block.type}`)
@ -81,7 +82,8 @@ class ContentBlock extends React.PureComponent<Props> {
icon={<TextIcon/>}
onClick={() => {
const newBlock = new MutableTextBlock()
newBlock.parentId = cardId
newBlock.parentId = card.id
newBlock.rootId = card.rootId
// TODO: Handle need to reorder all blocks
newBlock.order = OctoUtils.getOrderBefore(block, contents)
@ -96,7 +98,7 @@ class ContentBlock extends React.PureComponent<Props> {
onClick={() => {
Utils.selectLocalFile(
(file) => {
mutator.createImageBlock(cardId, file, OctoUtils.getOrderBefore(block, contents))
mutator.createImageBlock(card, file, OctoUtils.getOrderBefore(block, contents))
},
'.jpg,.jpeg,.png')
}}
@ -107,7 +109,8 @@ class ContentBlock extends React.PureComponent<Props> {
icon={<DividerIcon/>}
onClick={() => {
const newBlock = new MutableDividerBlock()
newBlock.parentId = cardId
newBlock.parentId = card.id
newBlock.rootId = card.rootId
// TODO: Handle need to reorder all blocks
newBlock.order = OctoUtils.getOrderBefore(block, contents)

View File

@ -334,10 +334,12 @@ class Sidebar extends React.Component<Props, State> {
const oldBoardId = this.props.activeBoardId
const board = new MutableBoard()
board.rootId = board.id
const view = new MutableBoardView()
view.viewType = 'board'
view.parentId = board.id
view.rootId = board.rootId
view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board View'})
await mutator.insertBlocks(
@ -412,6 +414,7 @@ class Sidebar extends React.Component<Props, State> {
const {activeBoardId} = this.props
const boardTemplate = new MutableBoard()
boardTemplate.rootId = boardTemplate.id
boardTemplate.isTemplate = true
await mutator.insertBlock(
boardTemplate,

View File

@ -323,6 +323,7 @@ class TableComponent extends React.Component<Props, State> {
const card = new MutableCard()
card.parentId = boardTree.board.id
card.rootId = boardTree.board.rootId
if (!card.icon) {
card.icon = BlockIcons.shared.randomIcon()
}
@ -346,6 +347,7 @@ class TableComponent extends React.Component<Props, State> {
const cardTemplate = new MutableCard()
cardTemplate.isTemplate = true
cardTemplate.parentId = boardTree.board.id
cardTemplate.rootId = boardTree.board.rootId
await mutator.insertBlock(
cardTemplate,
'add card template',

View File

@ -99,6 +99,7 @@ class ViewHeader extends React.Component<Props, State> {
for (let i = 0; i < count; i++) {
const card = new MutableCard()
card.parentId = boardTree.board.id
card.rootId = boardTree.board.rootId
card.properties = CardFilter.propertiesThatMeetFilterGroup(activeView.filter, board.cardProperties)
card.title = `Test Card ${startCount + i + 1}`
card.icon = BlockIcons.shared.randomIcon()

View File

@ -47,6 +47,7 @@ export class ViewMenu extends React.PureComponent<Props> {
view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board View'})
view.viewType = 'board'
view.parentId = board.id
view.rootId = board.rootId
const oldViewId = boardTree.activeView.id
@ -69,6 +70,7 @@ export class ViewMenu extends React.PureComponent<Props> {
view.title = intl.formatMessage({id: 'View.NewTableTitle', defaultMessage: 'Table View'})
view.viewType = 'table'
view.parentId = board.id
view.rootId = board.rootId
view.visiblePropertyIds = board.cardProperties.map((o) => o.id)
view.columnWidths = {}
view.columnWidths[Constants.titleColumnId] = Constants.defaultTitleColumnWidth

View File

@ -573,18 +573,19 @@ class Mutator {
}
// Not a mutator, but convenient to put here since Mutator wraps OctoClient
async importFullArchive(blocks: IBlock[]): Promise<Response> {
async importFullArchive(blocks: readonly IBlock[]): Promise<Response> {
return octoClient.importFullArchive(blocks)
}
async createImageBlock(parentId: string, file: File, order = 1000): Promise<IBlock | undefined> {
async createImageBlock(parent: IBlock, file: File, order = 1000): Promise<IBlock | undefined> {
const url = await octoClient.uploadFile(file)
if (!url) {
return undefined
}
const block = new MutableImageBlock()
block.parentId = parentId
block.parentId = parent.id
block.rootId = parent.rootId
block.order = order
block.url = url

View File

@ -30,7 +30,7 @@ class OctoClient {
return blocks
}
async importFullArchive(blocks: IBlock[]): Promise<Response> {
async importFullArchive(blocks: readonly IBlock[]): Promise<Response> {
Utils.log(`importFullArchive: ${blocks.length} blocks(s)`)
blocks.forEach((block) => {
Utils.log(`\t ${block.type}, ${block.id}`)

View File

@ -1,5 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {IBlock, MutableBlock} from './blocks/block'
import {IPropertyTemplate, MutableBoard} from './blocks/board'
import {MutableBoardView} from './blocks/boardView'
@ -88,7 +89,7 @@ class OctoUtils {
}
// Creates a copy of the blocks with new ids and parentIDs
static duplicateBlockTree(blocks: IBlock[], rootBlockId: string): [MutableBlock[], MutableBlock, Readonly<Record<string, string>>] {
static duplicateBlockTree(blocks: IBlock[], sourceBlockId: string): [MutableBlock[], MutableBlock, Readonly<Record<string, string>>] {
const idMap: Record<string, string> = {}
const newBlocks = blocks.map((block) => {
const newBlock = this.hydrateBlock(block)
@ -97,14 +98,29 @@ class OctoUtils {
return newBlock
})
const newRootBlockId = idMap[rootBlockId]
const newSourceBlockId = idMap[sourceBlockId]
// Determine the new rootId if needed
let newRootId: string
const sourceBlock = blocks.find((block) => block.id === sourceBlockId)!
if (sourceBlock.rootId === sourceBlock.id) {
// Special case: when duplicating a tree from root, remap all the descendant rootIds
const newSourceBlock = newBlocks.find((block) => block.id === newSourceBlockId)!
newRootId = newSourceBlock.id
}
newBlocks.forEach((newBlock) => {
// Note: Don't remap the parent of the new root block
if (newBlock.id !== newRootBlockId && newBlock.parentId) {
if (newBlock.id !== newSourceBlockId && newBlock.parentId) {
newBlock.parentId = idMap[newBlock.parentId] || newBlock.parentId
Utils.assert(newBlock.parentId, `Block ${newBlock.id} (${newBlock.type} ${newBlock.title}) has no parent`)
}
// Remap the rootIds if we are duplicating a tree from root
if (newRootId) {
newBlock.rootId = newRootId
}
// Remap manual card order
if (newBlock.type === 'view') {
const view = newBlock as MutableBoardView
@ -112,8 +128,8 @@ class OctoUtils {
}
})
const newRootBlock = newBlocks.find((block) => block.id === newRootBlockId)!
return [newBlocks, newRootBlock, idMap]
const newSourceBlock = newBlocks.find((block) => block.id === newSourceBlockId)!
return [newBlocks, newSourceBlock, idMap]
}
}

View File

@ -84,14 +84,13 @@ class MutableBoardTree implements BoardTree {
}
private ensureMinimumSchema(): boolean {
const {board} = this
let didChange = false
// At least one select property
const selectProperties = board?.cardProperties.find((o) => o.type === 'select')
const selectProperties = this.board?.cardProperties.find((o) => o.type === 'select')
if (!selectProperties) {
const newBoard = new MutableBoard(board)
const newBoard = new MutableBoard(this.board)
newBoard.rootId = newBoard.id
const property: IPropertyTemplate = {
id: Utils.createGuid(),
name: 'Status',
@ -106,8 +105,9 @@ class MutableBoardTree implements BoardTree {
// At least one view
if (this.views.length < 1) {
const view = new MutableBoardView()
view.parentId = board?.id
view.groupById = board?.cardProperties.find((o) => o.type === 'select')?.id
view.parentId = this.board.id
view.rootId = this.board.rootId
view.groupById = this.board.cardProperties.find((o) => o.type === 'select')?.id
this.views.push(view)
didChange = true
}