2022-02-10 13:58:26 +08:00
|
|
|
import { Repository, TransactionAble } from '@nocobase/database';
|
2022-01-19 10:09:30 +08:00
|
|
|
import lodash from 'lodash';
|
|
|
|
import { ChildOptions, SchemaNode, TargetPosition, UiSchemaNodeDAO } from './dao/ui_schema_node_dao';
|
|
|
|
import { uid } from '@nocobase/utils';
|
|
|
|
import { Transaction } from 'sequelize';
|
|
|
|
|
|
|
|
interface GetJsonSchemaOptions {
|
|
|
|
includeAsyncNode?: boolean;
|
|
|
|
transaction?: Transaction;
|
|
|
|
}
|
|
|
|
|
|
|
|
const nodeKeys = ['properties', 'definitions', 'patternProperties', 'additionalProperties', 'items'];
|
|
|
|
|
|
|
|
export default class UiSchemaRepository extends Repository {
|
|
|
|
static schemaToSingleNodes(schema: any, carry: SchemaNode[] = [], childOptions: ChildOptions = null): SchemaNode[] {
|
|
|
|
const node = lodash.cloneDeep(
|
|
|
|
lodash.isString(schema)
|
|
|
|
? {
|
|
|
|
'x-uid': schema,
|
|
|
|
}
|
|
|
|
: schema,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!lodash.get(node, 'name')) {
|
|
|
|
node.name = uid();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lodash.get(node, 'x-uid')) {
|
|
|
|
node['x-uid'] = uid();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (childOptions) {
|
|
|
|
node.childOptions = childOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
carry.push(node);
|
|
|
|
|
|
|
|
for (const nodeKey of nodeKeys) {
|
|
|
|
const nodeProperty = lodash.get(node, nodeKey);
|
|
|
|
|
|
|
|
// array items
|
|
|
|
if (nodeKey === 'items' && nodeProperty) {
|
|
|
|
const handleItems = lodash.isArray(nodeProperty) ? nodeProperty : [nodeProperty];
|
|
|
|
for (const item of handleItems) {
|
|
|
|
carry = this.schemaToSingleNodes(item, carry, {
|
|
|
|
parentUid: node['x-uid'],
|
|
|
|
type: nodeKey,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (lodash.isPlainObject(nodeProperty)) {
|
|
|
|
const subNodeNames = lodash.keys(lodash.get(node, nodeKey));
|
|
|
|
|
|
|
|
delete node[nodeKey];
|
|
|
|
|
|
|
|
for (const subNodeName of subNodeNames) {
|
|
|
|
const subSchema = {
|
|
|
|
name: subNodeName,
|
|
|
|
...lodash.get(nodeProperty, subNodeName),
|
|
|
|
};
|
|
|
|
|
|
|
|
carry = this.schemaToSingleNodes(subSchema, carry, {
|
|
|
|
parentUid: node['x-uid'],
|
|
|
|
type: nodeKey,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return carry;
|
|
|
|
}
|
|
|
|
|
|
|
|
async getProperties(uid: string) {
|
|
|
|
const db = this.database;
|
|
|
|
const treeCollection = db.getCollection('ui_schema_tree_path');
|
|
|
|
|
|
|
|
const rawSql = `
|
|
|
|
SELECT SchemaTable.uid as uid, SchemaTable.name as name, SchemaTable.schema as "schema",
|
|
|
|
TreePath.depth as depth,
|
|
|
|
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent
|
|
|
|
FROM ${treeCollection.model.tableName} as TreePath
|
|
|
|
LEFT JOIN ${this.model.tableName} as SchemaTable ON SchemaTable.uid = TreePath.descendant
|
|
|
|
LEFT JOIN ${treeCollection.model.tableName} as NodeInfo ON NodeInfo.descendant = SchemaTable.uid and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
|
|
|
LEFT JOIN ${treeCollection.model.tableName} as ParentPath ON (ParentPath.descendant = SchemaTable.uid AND ParentPath.depth = 1)
|
|
|
|
WHERE TreePath.ancestor = :ancestor AND (NodeInfo.async = false or TreePath.depth = 1)`;
|
|
|
|
|
|
|
|
const nodes = await db.sequelize.query(rawSql, {
|
|
|
|
replacements: {
|
|
|
|
ancestor: uid,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const schema = this.nodesToSchema(nodes[0], uid);
|
|
|
|
return lodash.pick(schema, ['type', 'properties']);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getJsonSchema(uid: string, options?: GetJsonSchemaOptions) {
|
|
|
|
const db = this.database;
|
|
|
|
const treeCollection = db.getCollection('ui_schema_tree_path');
|
|
|
|
|
|
|
|
const treeTable = treeCollection.model.tableName;
|
|
|
|
|
|
|
|
const rawSql = `
|
|
|
|
SELECT SchemaTable.uid as uid, SchemaTable.name as name, SchemaTable.schema as "schema" ,
|
|
|
|
TreePath.depth as depth,
|
|
|
|
NodeInfo.type as type, NodeInfo.async as async, ParentPath.ancestor as parent, ParentPath.sort as sort
|
|
|
|
FROM ${treeTable} as TreePath
|
|
|
|
LEFT JOIN ${this.model.tableName} as SchemaTable ON SchemaTable.uid = TreePath.descendant
|
|
|
|
LEFT JOIN ${treeTable} as NodeInfo ON NodeInfo.descendant = SchemaTable.uid and NodeInfo.descendant = NodeInfo.ancestor and NodeInfo.depth = 0
|
|
|
|
LEFT JOIN ${treeTable} as ParentPath ON (ParentPath.descendant = SchemaTable.uid AND ParentPath.depth = 1)
|
|
|
|
WHERE TreePath.ancestor = :ancestor ${options?.includeAsyncNode ? '' : 'AND (NodeInfo.async != true )'}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const nodes = await db.sequelize.query(rawSql, {
|
|
|
|
replacements: {
|
|
|
|
ancestor: uid,
|
|
|
|
},
|
|
|
|
transaction: options?.transaction,
|
|
|
|
});
|
|
|
|
|
|
|
|
const schema = this.nodesToSchema(nodes[0], uid);
|
|
|
|
return schema;
|
|
|
|
}
|
|
|
|
|
|
|
|
nodesToSchema(nodes, rootUid) {
|
|
|
|
const nodeAttributeSanitize = (node) => {
|
|
|
|
const schema = {
|
|
|
|
...(lodash.isPlainObject(node.schema) ? node.schema : JSON.parse(node.schema)),
|
|
|
|
...lodash.pick(node, [...nodeKeys, 'name']),
|
|
|
|
['x-uid']: node.uid,
|
|
|
|
['x-async']: !!node.async,
|
|
|
|
['x-index']: node.sort,
|
|
|
|
};
|
|
|
|
|
|
|
|
return schema;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildTree = (rootNode) => {
|
|
|
|
const children = nodes.filter((node) => node.parent == rootNode.uid);
|
|
|
|
|
|
|
|
if (children.length > 0) {
|
|
|
|
const childrenGroupByType = lodash.groupBy(children, 'type');
|
|
|
|
|
|
|
|
for (const childType of Object.keys(childrenGroupByType)) {
|
|
|
|
const properties = childrenGroupByType[childType]
|
|
|
|
.map((child) => buildTree(child))
|
|
|
|
.sort((a, b) => a['x-index'] - b['x-index']) as any;
|
|
|
|
|
|
|
|
rootNode[childType] =
|
|
|
|
childType == 'items'
|
|
|
|
? properties.length == 1
|
|
|
|
? properties[0]
|
|
|
|
: properties
|
|
|
|
: properties.reduce((carry, item) => {
|
|
|
|
carry[item.name] = item;
|
|
|
|
delete item['name'];
|
|
|
|
return carry;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nodeAttributeSanitize(rootNode);
|
|
|
|
};
|
|
|
|
|
|
|
|
return buildTree(nodes.find((node) => node.uid == rootUid));
|
|
|
|
}
|
|
|
|
|
|
|
|
treeCollection() {
|
|
|
|
return this.database.getCollection('ui_schema_tree_path');
|
|
|
|
}
|
|
|
|
|
|
|
|
async patch(newSchema: any, options?) {
|
|
|
|
let handleTransaction = true;
|
|
|
|
let transaction;
|
|
|
|
if (options?.transaction) {
|
|
|
|
handleTransaction = false;
|
|
|
|
transaction = options.transaction;
|
|
|
|
} else {
|
|
|
|
transaction = await this.database.sequelize.transaction();
|
|
|
|
}
|
|
|
|
|
|
|
|
const rootUid = newSchema['x-uid'];
|
|
|
|
const oldTree = await this.getJsonSchema(rootUid);
|
|
|
|
|
|
|
|
const traverSchemaTree = async (schema, path = []) => {
|
|
|
|
const node = schema;
|
|
|
|
const oldNode = path.length == 0 ? oldTree : lodash.get(oldTree, path);
|
|
|
|
const oldNodeUid = oldNode['x-uid'];
|
|
|
|
|
|
|
|
await this.updateNode(oldNodeUid, node, transaction);
|
|
|
|
|
|
|
|
const properties = node.properties;
|
|
|
|
if (lodash.isPlainObject(properties)) {
|
|
|
|
for (const name of Object.keys(properties)) {
|
|
|
|
await traverSchemaTree(properties[name], [...path, 'properties', name]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
await traverSchemaTree(newSchema);
|
|
|
|
|
|
|
|
handleTransaction && (await transaction.commit());
|
|
|
|
} catch (err) {
|
|
|
|
handleTransaction && (await transaction.rollback());
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateNode(uid: string, schema: any, transaction?: Transaction) {
|
|
|
|
const nodeModel = await this.findOne({
|
|
|
|
filter: {
|
|
|
|
uid,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
await nodeModel.update(
|
|
|
|
{
|
|
|
|
schema: {
|
|
|
|
...(nodeModel.get('schema') as any),
|
|
|
|
...lodash.omit(schema, ['x-async', 'name', 'x-uid', 'properties']),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
hooks: false,
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-10 20:28:27 +08:00
|
|
|
protected async isSingleChild(uid, transaction) {
|
|
|
|
const db = this.database;
|
|
|
|
|
|
|
|
const parent = await db.getRepository('ui_schema_tree_path').findOne({
|
|
|
|
filter: {
|
|
|
|
descendant: uid,
|
|
|
|
depth: 1,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-02-10 21:06:22 +08:00
|
|
|
if (!parent) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-02-10 20:28:27 +08:00
|
|
|
const countResult = await db.sequelize.query(
|
|
|
|
`SELECT COUNT(*) FROM ${
|
|
|
|
db.getCollection('ui_schema_tree_path').model.tableName
|
|
|
|
} where ancestor = :ancestor and depth = 1`,
|
|
|
|
{
|
|
|
|
replacements: {
|
|
|
|
ancestor: parent.get('ancestor'),
|
|
|
|
},
|
|
|
|
type: 'SELECT',
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const parentChildrenCount = countResult[0]['count'];
|
|
|
|
|
|
|
|
if (parentChildrenCount == 1) {
|
2022-02-10 21:06:22 +08:00
|
|
|
const schema = await db.getRepository('ui_schemas').findOne({
|
|
|
|
filter: {
|
|
|
|
uid: parent.get('ancestor') as string,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return schema;
|
2022-02-10 20:28:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
async removeEmptyParents(options: TransactionAble & { uid: string; breakComponent?: string }) {
|
|
|
|
const { transaction, uid, breakComponent } = options;
|
|
|
|
|
|
|
|
const removeParent = async (nodeUid: string) => {
|
2022-02-10 21:06:22 +08:00
|
|
|
const parent = await this.isSingleChild(nodeUid, transaction);
|
|
|
|
|
|
|
|
const nodeComponentType = parent ? parent.get('x-component') : null;
|
2022-02-10 20:28:27 +08:00
|
|
|
|
2022-02-10 21:06:22 +08:00
|
|
|
if ((parent && !breakComponent) || (parent && breakComponent != nodeComponentType)) {
|
|
|
|
await removeParent(parent.get('uid') as string);
|
2022-02-10 20:28:27 +08:00
|
|
|
} else {
|
|
|
|
await this.remove(nodeUid, { transaction });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
await removeParent(uid);
|
|
|
|
}
|
|
|
|
|
2022-02-10 13:58:26 +08:00
|
|
|
async remove(uid: string, options?: TransactionAble) {
|
2022-01-19 10:09:30 +08:00
|
|
|
let handleTransaction: boolean = true;
|
|
|
|
let transaction;
|
|
|
|
|
|
|
|
if (options?.transaction) {
|
|
|
|
transaction = options.transaction;
|
|
|
|
handleTransaction = false;
|
|
|
|
} else {
|
|
|
|
transaction = await this.database.sequelize.transaction();
|
|
|
|
}
|
|
|
|
|
|
|
|
const treePathTable = this.treeCollection().model.tableName;
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.database.sequelize.query(
|
|
|
|
`DELETE FROM ${this.model.tableName} WHERE uid IN (
|
|
|
|
SELECT descendant FROM ${treePathTable} WHERE ancestor = :uid
|
|
|
|
)
|
|
|
|
`,
|
|
|
|
{
|
|
|
|
replacements: {
|
|
|
|
uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
await this.database.sequelize.query(
|
|
|
|
`
|
|
|
|
DELETE FROM ${treePathTable}
|
|
|
|
WHERE descendant IN (
|
|
|
|
select descendant FROM
|
|
|
|
(SELECT descendant
|
|
|
|
FROM ${treePathTable}
|
|
|
|
WHERE ancestor = :uid)as descendantTable) `,
|
|
|
|
{
|
|
|
|
replacements: {
|
|
|
|
uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (handleTransaction) {
|
|
|
|
await transaction.commit();
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
if (handleTransaction) {
|
|
|
|
await transaction.rollback();
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertBeside(targetUid: string, schema: any, side: 'before' | 'after') {
|
|
|
|
const targetParent = await this.treeCollection().repository.findOne({
|
|
|
|
filter: {
|
|
|
|
descendant: targetUid,
|
|
|
|
depth: 1,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const db = this.database;
|
|
|
|
const treeTable = this.treeCollection().model.tableName;
|
|
|
|
const typeQuery = await db.sequelize.query(`SELECT type from ${treeTable} WHERE ancestor = :uid AND depth = 0;`, {
|
|
|
|
type: 'SELECT',
|
|
|
|
replacements: {
|
|
|
|
uid: targetUid,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
|
|
|
|
|
|
|
const rootNode = nodes[0];
|
|
|
|
|
|
|
|
rootNode.childOptions = {
|
|
|
|
parentUid: targetParent.get('ancestor') as string,
|
|
|
|
type: typeQuery[0]['type'],
|
|
|
|
position: {
|
|
|
|
type: side,
|
|
|
|
target: targetUid,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
const insertedNodes = await this.insertNodes(nodes);
|
|
|
|
return await this.getJsonSchema(insertedNodes[0].get('uid'));
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertInner(targetUid: string, schema: any, position: 'first' | 'last') {
|
|
|
|
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
|
|
|
const rootNode = nodes[0];
|
|
|
|
rootNode.childOptions = {
|
|
|
|
parentUid: targetUid,
|
|
|
|
type: lodash.get(schema, 'x-node-type', 'properties'),
|
|
|
|
position,
|
|
|
|
};
|
|
|
|
|
|
|
|
const insertedNodes = await this.insertNodes(nodes);
|
|
|
|
return await this.getJsonSchema(insertedNodes[0].get('uid'));
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertAdjacent(position: 'beforeBegin' | 'afterBegin' | 'beforeEnd' | 'afterEnd', target: string, schema: any) {
|
|
|
|
return await this[`insert${lodash.upperFirst(position)}`](target, schema);
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertAfterBegin(targetUid: string, schema: any) {
|
|
|
|
return await this.insertInner(targetUid, schema, 'first');
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertBeforeEnd(targetUid: string, schema: any) {
|
|
|
|
return await this.insertInner(targetUid, schema, 'last');
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertBeforeBegin(targetUid: string, schema: any) {
|
|
|
|
return await this.insertBeside(targetUid, schema, 'before');
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertAfterEnd(targetUid: string, schema: any) {
|
|
|
|
return await this.insertBeside(targetUid, schema, 'after');
|
|
|
|
}
|
|
|
|
|
|
|
|
async insertNodes(nodes: SchemaNode[], options?) {
|
|
|
|
let handleTransaction: boolean = true;
|
|
|
|
let transaction;
|
|
|
|
|
|
|
|
if (options?.transaction) {
|
|
|
|
transaction = options.transaction;
|
|
|
|
handleTransaction = false;
|
|
|
|
} else {
|
|
|
|
transaction = await this.database.sequelize.transaction();
|
|
|
|
}
|
|
|
|
|
|
|
|
const insertedNodes = [];
|
|
|
|
|
|
|
|
try {
|
|
|
|
for (const node of nodes) {
|
|
|
|
insertedNodes.push(await this.insertSingleNode(node, transaction));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (handleTransaction) {
|
|
|
|
await transaction.commit();
|
|
|
|
}
|
|
|
|
return insertedNodes;
|
|
|
|
} catch (err) {
|
2022-02-10 19:11:43 +08:00
|
|
|
console.log({ err });
|
2022-01-19 10:09:30 +08:00
|
|
|
if (handleTransaction) {
|
|
|
|
await transaction.rollback();
|
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-10 22:53:26 +08:00
|
|
|
async insert(schema: any, options?: TransactionAble) {
|
2022-01-19 10:09:30 +08:00
|
|
|
const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
|
|
|
|
const insertedNodes = await this.insertNodes(nodes, options);
|
|
|
|
return this.getJsonSchema(insertedNodes[0].get('uid'), {
|
|
|
|
transaction: options?.transaction,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-02-08 16:36:48 +08:00
|
|
|
private async insertSchemaRecord(name, uid, schema, transaction) {
|
2022-02-09 20:24:25 +08:00
|
|
|
const serverHooks = schema['x-server-hooks'] || [];
|
|
|
|
|
2022-02-08 16:36:48 +08:00
|
|
|
const node = await this.create({
|
|
|
|
values: {
|
|
|
|
name,
|
|
|
|
uid,
|
|
|
|
schema,
|
2022-02-09 20:24:25 +08:00
|
|
|
serverHooks,
|
2022-02-08 16:36:48 +08:00
|
|
|
},
|
|
|
|
transaction,
|
2022-02-10 22:53:26 +08:00
|
|
|
context: {
|
|
|
|
disableInsertHook: true,
|
|
|
|
},
|
2022-02-08 16:36:48 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:09:30 +08:00
|
|
|
async insertSingleNode(schema: SchemaNode, transaction: Transaction) {
|
|
|
|
const db = this.database;
|
|
|
|
const treeCollection = db.getCollection('ui_schema_tree_path');
|
|
|
|
|
|
|
|
const uid = schema['x-uid'];
|
|
|
|
const name = schema['name'];
|
|
|
|
const async = lodash.get(schema, 'x-async', false);
|
|
|
|
const childOptions = schema['childOptions'];
|
|
|
|
|
|
|
|
delete schema['x-uid'];
|
|
|
|
delete schema['x-async'];
|
|
|
|
delete schema['name'];
|
|
|
|
delete schema['childOptions'];
|
|
|
|
|
|
|
|
let savedNode;
|
|
|
|
|
|
|
|
// check node exists or not
|
|
|
|
const existsNode = await this.findOne({
|
|
|
|
filter: {
|
|
|
|
uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
});
|
|
|
|
|
|
|
|
const treeTable = treeCollection.model.tableName;
|
|
|
|
|
|
|
|
if (existsNode) {
|
|
|
|
savedNode = existsNode;
|
|
|
|
} else {
|
2022-02-08 16:36:48 +08:00
|
|
|
savedNode = await this.insertSchemaRecord(name, uid, schema, transaction);
|
2022-01-19 10:09:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (childOptions) {
|
|
|
|
const parentUid = childOptions.parentUid;
|
|
|
|
|
|
|
|
const isTreeQuery = await db.sequelize.query(
|
|
|
|
`SELECT COUNT(*) as childrenCount from ${treeTable} WHERE ancestor = :ancestor AND descendant != ancestor`,
|
|
|
|
{
|
|
|
|
type: 'SELECT',
|
|
|
|
replacements: {
|
|
|
|
ancestor: uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
const isTree = isTreeQuery[0]['childrenCount'];
|
|
|
|
|
|
|
|
// if node is a tree root move tree to new path
|
|
|
|
if (isTree) {
|
|
|
|
await db.sequelize.query(
|
|
|
|
`DELETE FROM ${treeTable}
|
|
|
|
WHERE descendant IN (SELECT descendant FROM (SELECT descendant FROM ${treeTable} WHERE ancestor = :uid) as descendantTable )
|
|
|
|
AND ancestor IN (SELECT ancestor FROM (SELECT ancestor FROM ${treeTable} WHERE descendant = :uid AND ancestor != descendant) as ancestorTable)
|
|
|
|
`,
|
|
|
|
{
|
|
|
|
type: 'DELETE',
|
|
|
|
replacements: {
|
|
|
|
uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
await db.sequelize.query(
|
|
|
|
`INSERT INTO ${treeTable} (ancestor, descendant, depth)
|
|
|
|
SELECT supertree.ancestor, subtree.descendant, supertree.depth + subtree.depth + 1
|
|
|
|
FROM ${treeTable} AS supertree
|
|
|
|
CROSS JOIN ${treeTable} AS subtree
|
|
|
|
WHERE supertree.descendant = :parentUid
|
|
|
|
AND subtree.ancestor = :uid;`,
|
|
|
|
{
|
|
|
|
type: 'INSERT',
|
|
|
|
replacements: {
|
|
|
|
uid,
|
|
|
|
parentUid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isTree) {
|
|
|
|
if (existsNode) {
|
|
|
|
// remove old path
|
|
|
|
await db.sequelize.query(`DELETE FROM ${treeTable} WHERE descendant = :uid AND ancestor != descendant`, {
|
|
|
|
type: 'DELETE',
|
|
|
|
replacements: {
|
|
|
|
uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// insert tree path
|
|
|
|
await db.sequelize.query(
|
|
|
|
`INSERT INTO ${treeTable} (ancestor, descendant, depth)
|
|
|
|
SELECT t.ancestor, :modelKey, depth + 1 FROM ${treeTable} AS t WHERE t.descendant = :modelParentKey `,
|
|
|
|
{
|
|
|
|
type: 'INSERT',
|
|
|
|
transaction,
|
|
|
|
replacements: {
|
|
|
|
modelKey: savedNode.get('uid'),
|
|
|
|
modelParentKey: parentUid,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!existsNode) {
|
|
|
|
// insert type && async
|
|
|
|
await db.sequelize.query(
|
|
|
|
`INSERT INTO ${treeTable}(ancestor, descendant, depth, type, async) VALUES (:modelKey, :modelKey, 0, :type, :async )`,
|
|
|
|
{
|
|
|
|
type: 'INSERT',
|
|
|
|
replacements: {
|
|
|
|
modelKey: savedNode.get('uid'),
|
|
|
|
type: childOptions.type,
|
|
|
|
async,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const nodePosition = childOptions.position || 'last';
|
|
|
|
|
|
|
|
let sort;
|
|
|
|
|
|
|
|
// insert at first
|
|
|
|
if (nodePosition === 'first') {
|
|
|
|
sort = 1;
|
|
|
|
// move all child last index
|
|
|
|
await db.sequelize.query(
|
|
|
|
`UPDATE ${treeTable} as TreeTable
|
|
|
|
SET sort = TreeTable.sort + 1
|
|
|
|
FROM ${treeTable} as NodeInfo
|
|
|
|
WHERE NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0
|
|
|
|
AND TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`,
|
|
|
|
{
|
|
|
|
replacements: {
|
|
|
|
ancestor: childOptions.parentUid,
|
|
|
|
type: childOptions.type,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nodePosition === 'last') {
|
|
|
|
const maxSort = await db.sequelize.query(
|
|
|
|
`SELECT ${
|
|
|
|
this.database.sequelize.getDialect() === 'postgres' ? 'coalesce' : 'ifnull'
|
|
|
|
}(MAX(TreeTable.sort), 0) as maxsort FROM ${treeTable} as TreeTable
|
|
|
|
LEFT JOIN ${treeTable} as NodeInfo
|
|
|
|
ON NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0
|
|
|
|
WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and NodeInfo.type = :type`,
|
|
|
|
{
|
|
|
|
type: 'SELECT',
|
|
|
|
replacements: {
|
|
|
|
ancestor: childOptions.parentUid,
|
|
|
|
type: childOptions.type,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
sort = parseInt(maxSort[0]['maxsort']) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lodash.isPlainObject(nodePosition)) {
|
|
|
|
const targetPosition = nodePosition as TargetPosition;
|
|
|
|
const target = targetPosition.target;
|
|
|
|
|
|
|
|
const targetSort = await db.sequelize.query(
|
|
|
|
`SELECT TreeTable.sort as sort FROM ${treeTable} as TreeTable
|
|
|
|
LEFT JOIN ${treeTable} as NodeInfo
|
|
|
|
ON NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0 WHERE TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor AND TreeTable.descendant = :descendant and NodeInfo.type = :type`,
|
|
|
|
{
|
|
|
|
type: 'SELECT',
|
|
|
|
replacements: {
|
|
|
|
ancestor: childOptions.parentUid,
|
|
|
|
descendant: target,
|
|
|
|
type: childOptions.type,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
sort = targetSort[0].sort;
|
|
|
|
|
|
|
|
if (targetPosition.type == 'after') {
|
|
|
|
sort += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
await db.sequelize.query(
|
|
|
|
`UPDATE ${treeTable} as TreeTable
|
|
|
|
SET sort = TreeTable.sort + 1
|
|
|
|
FROM ${treeTable} as NodeInfo
|
|
|
|
WHERE NodeInfo.descendant = TreeTable.descendant and NodeInfo.depth = 0
|
|
|
|
AND TreeTable.depth = 1 AND TreeTable.ancestor = :ancestor and TreeTable.sort >= :sort and NodeInfo.type = :type`,
|
|
|
|
{
|
|
|
|
replacements: {
|
|
|
|
ancestor: childOptions.parentUid,
|
|
|
|
sort,
|
|
|
|
type: childOptions.type,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// update order
|
|
|
|
const updateSql = `UPDATE ${treeTable} SET sort = :sort WHERE depth = 1 AND ancestor = :ancestor AND descendant = :descendant`;
|
|
|
|
await db.sequelize.query(updateSql, {
|
|
|
|
type: 'UPDATE',
|
|
|
|
replacements: {
|
|
|
|
ancestor: childOptions.parentUid,
|
|
|
|
sort,
|
|
|
|
descendant: uid,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// insert root node path
|
|
|
|
await db.sequelize.query(
|
|
|
|
`INSERT INTO ${treeTable}(ancestor, descendant, depth, async) VALUES (:modelKey, :modelKey, 0, :async )`,
|
|
|
|
{
|
|
|
|
type: 'INSERT',
|
|
|
|
replacements: {
|
|
|
|
modelKey: savedNode.get('uid'),
|
|
|
|
async,
|
|
|
|
},
|
|
|
|
transaction,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return savedNode;
|
|
|
|
}
|
|
|
|
}
|