711 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			711 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { Repository, TransactionAble } from '@nocobase/database';
 | |
| import { uid } from '@nocobase/utils';
 | |
| import lodash from 'lodash';
 | |
| import { Transaction } from 'sequelize';
 | |
| import { ChildOptions, SchemaNode, TargetPosition } from './dao/ui_schema_node_dao';
 | |
| 
 | |
| interface GetJsonSchemaOptions {
 | |
|   includeAsyncNode?: boolean;
 | |
|   transaction?: Transaction;
 | |
| }
 | |
| 
 | |
| const nodeKeys = ['properties', 'definitions', 'patternProperties', 'additionalProperties', 'items'];
 | |
| 
 | |
| export 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, ParentPath.sort as sort
 | |
|         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,
 | |
|       },
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   protected async isSingleChild(uid, transaction) {
 | |
|     const db = this.database;
 | |
| 
 | |
|     const parent = await db.getRepository('ui_schema_tree_path').findOne({
 | |
|       filter: {
 | |
|         descendant: uid,
 | |
|         depth: 1,
 | |
|       },
 | |
|     });
 | |
| 
 | |
|     if (!parent) {
 | |
|       return null;
 | |
|     }
 | |
| 
 | |
|     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) {
 | |
|       const schema = await db.getRepository('ui_schemas').findOne({
 | |
|         filter: {
 | |
|           uid: parent.get('ancestor') as string,
 | |
|         },
 | |
|       });
 | |
| 
 | |
|       return schema;
 | |
|     }
 | |
| 
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   async removeEmptyParents(options: TransactionAble & { uid: string; breakComponent?: string }) {
 | |
|     const { transaction, uid, breakComponent } = options;
 | |
| 
 | |
|     const removeParent = async (nodeUid: string) => {
 | |
|       const parent = await this.isSingleChild(nodeUid, transaction);
 | |
| 
 | |
|       const nodeComponentType = parent ? parent.get('x-component') : null;
 | |
| 
 | |
|       if ((parent && !breakComponent) || (parent && breakComponent != nodeComponentType)) {
 | |
|         await removeParent(parent.get('uid') as string);
 | |
|       } else {
 | |
|         await this.remove(nodeUid, { transaction });
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     await removeParent(uid);
 | |
|   }
 | |
| 
 | |
|   async remove(uid: string, options?: TransactionAble) {
 | |
|     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) {
 | |
|       console.log({ err });
 | |
|       if (handleTransaction) {
 | |
|         await transaction.rollback();
 | |
|       }
 | |
|       throw err;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async insert(schema: any, options?: TransactionAble) {
 | |
|     const nodes = UiSchemaRepository.schemaToSingleNodes(schema);
 | |
|     const insertedNodes = await this.insertNodes(nodes, options);
 | |
|     return this.getJsonSchema(insertedNodes[0].get('uid'), {
 | |
|       transaction: options?.transaction,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   private async insertSchemaRecord(name, uid, schema, transaction) {
 | |
|     const serverHooks = schema['x-server-hooks'] || [];
 | |
| 
 | |
|     const node = await this.create({
 | |
|       values: {
 | |
|         name,
 | |
|         uid,
 | |
|         schema,
 | |
|         serverHooks,
 | |
|       },
 | |
|       transaction,
 | |
|       context: {
 | |
|         disableInsertHook: true,
 | |
|       },
 | |
|     });
 | |
| 
 | |
|     return node;
 | |
|   }
 | |
| 
 | |
|   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 {
 | |
|       savedNode = await this.insertSchemaRecord(name, uid, schema, transaction);
 | |
|     }
 | |
| 
 | |
|     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;
 | |
|   }
 | |
| }
 | |
| 
 | |
| export default UiSchemaRepository;
 |