From 406e5a79e6f6aa5b153e570f32bbdea223eba50b Mon Sep 17 00:00:00 2001 From: sovereign Date: Thu, 20 May 2021 12:58:40 +1000 Subject: [PATCH 1/4] =?UTF-8?q?IONEXAMPLE-196=20=D0=B8=D0=BC=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=82=20xls=20=D0=B2=20=D1=81=D1=82=D1=83=D0=B4=D0=B8?= =?UTF-8?q?=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/xls/appMaker.js | 131 ++++++++++++++++++ backend/xls/dbtypes/xls.js | 99 +++++++++++++ backend/xls/metadataStructure/applyFilter.js | 75 ++++++++++ backend/xls/metadataStructure/constants.json | 7 + .../xls/metadataStructure/deduceMetadata.js | 12 ++ .../xls/metadataStructure/filters/email.json | 11 ++ .../xls/metadataStructure/filters/phone.json | 29 ++++ backend/xls/metadataStructure/formDeploy.js | 16 +++ backend/xls/metadataStructure/index.js | 7 + .../writeMetadataStructure.js | 58 ++++++++ .../xls/metadataStructure/writeNavigation.js | 42 ++++++ .../xls/metadataStructure/writeViewforms.js | 74 ++++++++++ .../objectStructure/expandObjectStructure.js | 56 ++++++++ backend/xls/objectStructure/getObjects.js | 17 +++ backend/xls/objectStructure/index.js | 6 + .../objectStructure/prepareObjectStructure.js | 33 +++++ .../objectStructure/writeObjectStructure.js | 27 ++++ backend/xls/target/data.json | 6 + backend/xls/target/deploy.json | 17 +++ backend/xls/target/metadata.class.json | 48 +++++++ .../xls/target/metadataProperty/object.json | 29 ++++ .../xls/target/metadataProperty/string.json | 29 ++++ backend/xls/target/navigationNode.json | 15 ++ .../xls/target/navigationSection.section.json | 7 + backend/xls/target/viewCreate.json | 31 +++++ backend/xls/target/viewItem.json | 31 +++++ backend/xls/target/viewList.json | 35 +++++ backend/xls/target/viewProperty/object.json | 52 +++++++ backend/xls/target/viewProperty/string.json | 21 +++ backend/xls/util/misc.js | 46 ++++++ controllers/dbToApp.js | 26 ++++ controllers/index.js | 3 +- package.json | 6 +- standalone | 4 +- themes/portal/static/css/studio.css | 9 +- .../static/js/studio/components/tabs.js | 14 ++ themes/portal/templates/parts/tabs.ejs | 4 + 37 files changed, 1128 insertions(+), 5 deletions(-) create mode 100644 backend/xls/appMaker.js create mode 100644 backend/xls/dbtypes/xls.js create mode 100644 backend/xls/metadataStructure/applyFilter.js create mode 100644 backend/xls/metadataStructure/constants.json create mode 100644 backend/xls/metadataStructure/deduceMetadata.js create mode 100644 backend/xls/metadataStructure/filters/email.json create mode 100644 backend/xls/metadataStructure/filters/phone.json create mode 100644 backend/xls/metadataStructure/formDeploy.js create mode 100644 backend/xls/metadataStructure/index.js create mode 100644 backend/xls/metadataStructure/writeMetadataStructure.js create mode 100644 backend/xls/metadataStructure/writeNavigation.js create mode 100644 backend/xls/metadataStructure/writeViewforms.js create mode 100644 backend/xls/objectStructure/expandObjectStructure.js create mode 100644 backend/xls/objectStructure/getObjects.js create mode 100644 backend/xls/objectStructure/index.js create mode 100644 backend/xls/objectStructure/prepareObjectStructure.js create mode 100644 backend/xls/objectStructure/writeObjectStructure.js create mode 100644 backend/xls/target/data.json create mode 100644 backend/xls/target/deploy.json create mode 100644 backend/xls/target/metadata.class.json create mode 100644 backend/xls/target/metadataProperty/object.json create mode 100644 backend/xls/target/metadataProperty/string.json create mode 100644 backend/xls/target/navigationNode.json create mode 100644 backend/xls/target/navigationSection.section.json create mode 100644 backend/xls/target/viewCreate.json create mode 100644 backend/xls/target/viewItem.json create mode 100644 backend/xls/target/viewList.json create mode 100644 backend/xls/target/viewProperty/object.json create mode 100644 backend/xls/target/viewProperty/string.json create mode 100644 backend/xls/util/misc.js create mode 100644 controllers/dbToApp.js diff --git a/backend/xls/appMaker.js b/backend/xls/appMaker.js new file mode 100644 index 0000000..e1bae3d --- /dev/null +++ b/backend/xls/appMaker.js @@ -0,0 +1,131 @@ +const fs = require('fs'); +const uuid = require('uuid'); +const path = require('path'); +const metadataStructureUtil = require('./metadataStructure/index'); +const objectStructureUtil = require('./objectStructure/index'); +const archiver = require('archiver'); +const miscUtil = require('./util/misc'); + +class appMaker { + + constructor(appName) { + this.name = appName; + } + + async prepareAppStructure() { + const includedFolders = [ + 'data', + 'meta', + 'navigation', + 'themes', + 'views', + 'workflows' + ]; + + this.folders = {}; + this.folders['app'] = await prepareAppStructure(includedFolders); + for (const folderName of includedFolders) + this.folders[folderName] = path.join(this.folders.app, folderName); + + return this.folders; + } + + async writePackageJson(moduleDependencies, appDependencies) { + this.moduleDependencies = moduleDependencies; + this.appDependencies = appDependencies; + this.packageJson = await formPackageJson(this.name, this.moduleDependencies, this.appDependencies); + + const packageJsonPath = path.join(this.folders.app, 'package.json'); + fs.writeFileSync(packageJsonPath, JSON.stringify(this.packageJson, null, 2)); + return packageJsonPath; + } + + async writeDeploy() { + this.deploy = await metadataStructureUtil.formDeploy(this.packageJson); + + const deployPath = path.join(this.folders.app, 'deploy.json'); + fs.writeFileSync(deployPath, JSON.stringify(this.deploy, null, 2)); + return deployPath; + } + + async writeMetadataStructure(metadataStructure) { + return await metadataStructureUtil.writeMetadataStructure(metadataStructure, path.join(this.folders.app, 'meta')); + } + + async fromXls(xls) { + await this.buildApp(await objectStructureUtil.getObjects(xls)); + return await this.zip(); + } + + async buildApp(source) { + await this.prepareAppStructure(); + this.objectStructure = objectStructureUtil.prepareObjectStructure(source); + this.metadataStructure = await metadataStructureUtil.deduceMetadata(this.objectStructure); + await metadataStructureUtil.writeMetadataStructure(this.metadataStructure, this.name, this.folders.meta); + await this.writePackageJson({'registry': '*'}, {}); + await this.writeDeploy(); + this.objectStructure = await objectStructureUtil.expandObjectStructure(this.objectStructure, this.metadataStructure); + await objectStructureUtil.writeObjectStructure(this.objectStructure, this.name, this.folders.data); + await metadataStructureUtil.writeNavigation(this.metadataStructure, this.folders.navigation); + await metadataStructureUtil.writeViewforms(this.metadataStructure, this.folders.views) + } + + zip() { + return new Promise((resolve, reject) => { + const archive = archiver('zip', { + zlib: {level: 9} + }); + archive.directory(this.folders.app, this.name, {name: this.name}); + let chunks = []; + let data; + archive.on('data', (chunk) => { + chunks = chunks.concat(chunk); + }); + archive.on('end', () => { + data = Buffer.concat(chunks); + resolve(data); + }); + archive.finalize(); + }); + } + + async clean() { + return await miscUtil.rmRecursively(this.folders.app); + } +} + +async function prepareAppStructure(includedFolders) { + const tempFolder = path.join(__dirname, 'temp'); + if (!fs.existsSync(tempFolder)) + await fs.mkdirSync(tempFolder); + const appFolder = path.join(tempFolder, uuid.v4()); + await fs.mkdirSync(appFolder); + + const fsPromises = []; + for (const folder of includedFolders) { + fsPromises.push(new Promise((resolve, reject) => { + fs.mkdir(path.join(appFolder, folder), null, (err) => { + if (err) + return reject(err); + return resolve(); + }); + })); + } + await Promise.all(fsPromises); + return appFolder; +} + +async function formPackageJson(appName, moduleDependencies, appDependencies) { + const packageJson = { + name: appName, + description: '', + version: '1.0.0', + ionModulesDependencies: {}, + ionMetaDependencies: {}, + }; + packageJson.ionModulesDependencies = moduleDependencies || {}; + packageJson.ionMetaDependencies = appDependencies || {}; + return packageJson; +} + +module.exports = appMaker; diff --git a/backend/xls/dbtypes/xls.js b/backend/xls/dbtypes/xls.js new file mode 100644 index 0000000..4350943 --- /dev/null +++ b/backend/xls/dbtypes/xls.js @@ -0,0 +1,99 @@ +const xlsx = require('xlsx'); + +/** + * Делает из xls или xlsx массив объектов + * @param {String} path - путь к файлу + * @param {boolean} compact - пропускать объекты, в которых все поля null + * @returns {{headers: [], data: []}} + */ +module.exports = (file, compact = true) => { + const xbook = xlsx.read(file); + + const out = {}; + + for (const sheetName of Object.keys(xbook.Sheets)) { + const sheet = xbook.Sheets[sheetName]; + out[sheetName] = {'headers': [], 'data': []}; + const topLeftmostCellAddress = sheet['!ref'].split(':')[0]; + const bottomRightmostCellAddress = sheet['!ref'].split(':')[1]; + const addressPattern = /^(\D+)(\d+)$/; + const leftmostColumn = addressPattern.exec(topLeftmostCellAddress)[1]; + const rightmostColumn = addressPattern.exec(bottomRightmostCellAddress)[1]; + const firstRow = parseInt(addressPattern.exec(topLeftmostCellAddress)[2]); + const lastRow = parseInt(addressPattern.exec(bottomRightmostCellAddress)[2]); + + if (!rightmostColumn) + continue; + + const headers = []; + const lastHeaderNum = lettersToIndex(rightmostColumn); + const firstHeaderNum = lettersToIndex(leftmostColumn); + for (let i = firstHeaderNum; i <= lastHeaderNum; i += 1) { + const column = indexToLetters(i); + if (sheet[column + firstRow]) + headers.push(sheet[column + firstRow].v); + else + headers.push(null); + } + const data = []; + for (let row = firstRow + 1; row <= lastRow; row += 1) { + const object = {}; + for (let column = firstHeaderNum; column <= lastHeaderNum; column += 1) { + const header = headers[column - firstHeaderNum]; + if (header == null) + continue; + const columnAddress = indexToLetters(column); + if (sheet[columnAddress + row]) + object[header] = sheet[columnAddress + row].v; + else + object[header] = null; + } + if (compact) { + let nullCheck = false; + for (const key of Object.keys(object)) + if (object[key] != null) { + nullCheck = true + break; + } + if (!nullCheck) + continue; + } + data.push(object); + } + out[sheetName].headers = headers; + out[sheetName].data = data; + } + + return out; + + function lettersToIndex(letters) { + const ALPHABET_POWER = 27; + let total = 0; + for (let i = letters.length - 1; i >= 0; i -= 1) { + const value = Math.pow(ALPHABET_POWER, letters.length - 1 - i) * (letters.charCodeAt(i) - 64); + total += value; + } + return total - 1 - Math.floor(total / ALPHABET_POWER); + } + + function indexToLetters(index) { + const ALPHABET_POWER = 27; + let indexValue = index + 1 + Math.floor((index) / (ALPHABET_POWER - 1)); + let out = ''; + let power = -1; + let multiplier; + do { + power += 1; + multiplier = Math.pow(ALPHABET_POWER, power + 1); + } while (indexValue / multiplier >= 1); + + while (power >= 0) { + multiplier = Math.pow(ALPHABET_POWER, power); + const charIndex = Math.floor(indexValue / multiplier); + indexValue %= multiplier; + out += String.fromCharCode(charIndex + 64); + power -= 1; + } + return out; + } +} diff --git a/backend/xls/metadataStructure/applyFilter.js b/backend/xls/metadataStructure/applyFilter.js new file mode 100644 index 0000000..57be786 --- /dev/null +++ b/backend/xls/metadataStructure/applyFilter.js @@ -0,0 +1,75 @@ +module.exports = { + all, + one +} + +const fs = require('fs'); +const path = require('path'); + +function all(objects, metadataStructure) { + const filters = fs.readdirSync(path.join(__dirname, 'filters')); + for (const filterFile of filters) { + const filterPath = path.join(__dirname, 'filters', filterFile); + const filter = require(filterPath); + one(filter, objects, metadataStructure); + } +} + +function one(filter, objects, metadataStructure) { + for (const object of objects) { + + if ( + (typeof object === 'object') + && (!Array.isArray(object)) + ) { + for (const key of Object.keys(object)) { + const value = object[key]; + const template = makeTemplate(value); + if (template) { + const filterTypes = Object.keys(filter); + for (const filterType of filterTypes) { + if ((RegExp(filterType).test(template))) { + metadataStructure[key] = filter[filterType].structure; + metadataStructure[key]['_deducedClass'] = filter[filterType].name; + metadataStructure[key]['_dataType'] = 'object'; + } + } + } + if (!metadataStructure[key]) { + metadataStructure[key] = {}; + metadataStructure[key]['_dataType'] = typeof value; + } + } + if (!metadataStructure['_dataType']) + metadataStructure['_dataType'] = 'object'; + + } else if ( + (typeof object === 'object') + && (Array.isArray(object)) + ) { + if (!metadataStructure['_dataType']) + metadataStructure['_dataType'] = 'collection'; + + } else { + if (!metadataStructure['_dataType']) + metadataStructure['_dataType'] = typeof object; + } + } +} + +function makeTemplate(value) { + let template = null; + switch(typeof value) { + case 'string': + template = value; + template = template.replace(/\d/g, '9'); + template = template.replace(/[a-zA-Z]/g, 'z'); + break; + case 'number': + template = String(value); + template = template.replace(/\d/g, '9'); + break; + } + + return template +} diff --git a/backend/xls/metadataStructure/constants.json b/backend/xls/metadataStructure/constants.json new file mode 100644 index 0000000..4d7b78d --- /dev/null +++ b/backend/xls/metadataStructure/constants.json @@ -0,0 +1,7 @@ +{ + "PROPERTY_NAMES_TO_IGNORE": [ + "_value", + "_deducedClass", + "_dataType" + ] +} \ No newline at end of file diff --git a/backend/xls/metadataStructure/deduceMetadata.js b/backend/xls/metadataStructure/deduceMetadata.js new file mode 100644 index 0000000..4c956d8 --- /dev/null +++ b/backend/xls/metadataStructure/deduceMetadata.js @@ -0,0 +1,12 @@ +const applyFilter = require('./applyFilter'); + +module.exports = async function deduceMetadata(objectStructure) { + const metadataStructure = {}; + + for (const objectClass of Object.keys(objectStructure)) { + metadataStructure[objectClass] = {}; + applyFilter.all(objectStructure[objectClass], metadataStructure[objectClass]); + } + + return metadataStructure; +} diff --git a/backend/xls/metadataStructure/filters/email.json b/backend/xls/metadataStructure/filters/email.json new file mode 100644 index 0000000..f350642 --- /dev/null +++ b/backend/xls/metadataStructure/filters/email.json @@ -0,0 +1,11 @@ +{ + "\\w+@\\w+?.\\w+": { + "name": "email", + "structure": { + "address": { + "_dataType": "string" + }, + "_value": "address" + } + } +} \ No newline at end of file diff --git a/backend/xls/metadataStructure/filters/phone.json b/backend/xls/metadataStructure/filters/phone.json new file mode 100644 index 0000000..7a56009 --- /dev/null +++ b/backend/xls/metadataStructure/filters/phone.json @@ -0,0 +1,29 @@ +{ + "\\+99999999999": { + "name": "phone", + "structure": { + "number": { + "_dataType": "string" + } + }, + "_value": "number" + }, + "\\+999999999999": { + "name": "phone", + "structure": { + "number": ":this" + } + }, + "\\+9999999999999": { + "name": "phone", + "structure": { + "number": ":this" + } + }, + "\\+\\d+?-999-999-9999": { + "name": "phone", + "structure": { + "number": ":this" + } + } +} \ No newline at end of file diff --git a/backend/xls/metadataStructure/formDeploy.js b/backend/xls/metadataStructure/formDeploy.js new file mode 100644 index 0000000..296b93a --- /dev/null +++ b/backend/xls/metadataStructure/formDeploy.js @@ -0,0 +1,16 @@ +module.exports = formDeploy; + +const deployTarget = require('../target/deploy.json'); + +async function formDeploy(packageJson) { + const deploy = Object.assign({}, deployTarget); + for (const moduleName of Object.keys(packageJson.ionModulesDependencies)) { + deploy.modules[moduleName] = { + 'globals': null, + 'import': null, + 'statics': null + }; + } + deploy['namespace'] = packageJson.name; + return deploy; +} diff --git a/backend/xls/metadataStructure/index.js b/backend/xls/metadataStructure/index.js new file mode 100644 index 0000000..acf45b4 --- /dev/null +++ b/backend/xls/metadataStructure/index.js @@ -0,0 +1,7 @@ +module.exports = { + deduceMetadata: require('./deduceMetadata'), + writeMetadataStructure: require('./writeMetadataStructure'), + writeNavigation: require('./writeNavigation'), + writeViewforms: require('./writeViewforms'), + formDeploy: require('./formDeploy') +} \ No newline at end of file diff --git a/backend/xls/metadataStructure/writeMetadataStructure.js b/backend/xls/metadataStructure/writeMetadataStructure.js new file mode 100644 index 0000000..eb50eff --- /dev/null +++ b/backend/xls/metadataStructure/writeMetadataStructure.js @@ -0,0 +1,58 @@ +module.exports = writeMetadataStructure; + +const fs = require('fs'); +const path = require('path'); +const {PROPERTY_NAMES_TO_IGNORE} = require('./constants.json'); + +const metadataTarget = require('../target/metadata.class.json'); +const propertyTypesDir = path.join(__dirname, '..', 'target', 'metadataProperty'); + +async function writeMetadataStructure(metadataStructure, namespace, destination) { + const writingPromises = []; + for (const dataClassName of Object.keys(metadataStructure)) + writingPromises.push(writeMetadataClass(metadataStructure[dataClassName], dataClassName, namespace, metadataTarget, destination)); + + return await Promise.all(writingPromises); +} + +async function writeMetadataClass(dataClass, name, namespace, target, destination) { + const newClass = JSON.parse(JSON.stringify(target)); + newClass['name'] = name; + newClass['caption'] = `${name[0].toUpperCase()}${name.slice(1)}`; + newClass['namespace'] = namespace; + if (dataClass['_value']) + newClass['semantic'] = dataClass['_value']; + const classPath = path.join(destination, `${name}.class.json`); + + const writingPromises = []; + + let propOrderNumber = 20; // 10 is GUID + + for (const propertyName of Object.keys(dataClass)) { + if (PROPERTY_NAMES_TO_IGNORE.includes(propertyName)) + continue; + const refClass = dataClass[propertyName]['_deducedClass']? + dataClass[propertyName]['_deducedClass'] + : propertyName; + const propertyType = dataClass[propertyName]._dataType; + const property = Object.assign({}, require(path.join(propertyTypesDir, `${propertyType}.json`))); + if (propertyType === 'object') { + if (!fs.existsSync(path.join(destination, `${refClass}.class.json`))) + writingPromises.push(writeMetadataClass(dataClass[propertyName], refClass, namespace, target, destination)); + property['refClass'] = refClass; + } + + property['name'] = propertyName; + property['caption'] = `${propertyName[0].toUpperCase()}${propertyName.slice(1)}`; + property['orderNumber'] = propOrderNumber; + propOrderNumber += 10; + + newClass.properties.push(property); + } + + writingPromises.push(new Promise((resolve, reject) => { + fs.writeFile(classPath, JSON.stringify(newClass, null, 2), () => resolve(classPath)) + })); + + return await Promise.all(writingPromises); +} diff --git a/backend/xls/metadataStructure/writeNavigation.js b/backend/xls/metadataStructure/writeNavigation.js new file mode 100644 index 0000000..1b41de0 --- /dev/null +++ b/backend/xls/metadataStructure/writeNavigation.js @@ -0,0 +1,42 @@ +module.exports = writeNavigation; + +const fs = require('fs'); +const path = require('path'); + +const navigationSectionTarget = require('../target/navigationSection.section.json'); +const navigationNodeTarget = require('../target/navigationNode.json'); + +async function writeNavigation(metadataStructure, destination) { + const writingPromises = []; + const navigationSectionPath = path.join(destination, 'generated.section.json'); + const navigationSection = Object.assign({}, navigationSectionTarget); + navigationSection['name'] = 'generated'; + navigationSection['caption'] = 'Generated'; + + writingPromises.push(new Promise((resolve, reject) => { + fs.writeFile(navigationSectionPath, JSON.stringify(navigationSection, null, 2), () => resolve(navigationSectionPath)) + })); + const nodesFolder = path.join(destination, 'generated'); + if (!fs.existsSync(nodesFolder)) + fs.mkdirSync(nodesFolder); + let nodeOrderNumber = 10; + for (const dataClassName of Object.keys(metadataStructure)) { + const nodePath = path.join(nodesFolder, `${dataClassName}.json`); + writingPromises.push(writeNavigationNode(metadataStructure[dataClassName], dataClassName, nodeOrderNumber, navigationNodeTarget, nodePath)); + nodeOrderNumber += 10; + } + + return await Promise.all(writingPromises); +} + +async function writeNavigationNode(dataClass, name, orderNumber, target, destination) { + const node = Object.assign({}, target); + node['code'] = name; + node['caption'] = `${name[0].toUpperCase()}${name.slice(1)}`; + node['classname'] = name; + node['orderNumber'] = orderNumber; + + return new Promise((resolve, reject) => { + fs.writeFile(destination, JSON.stringify(node, null, 2), () => resolve(destination)) + }); +} diff --git a/backend/xls/metadataStructure/writeViewforms.js b/backend/xls/metadataStructure/writeViewforms.js new file mode 100644 index 0000000..2c03e64 --- /dev/null +++ b/backend/xls/metadataStructure/writeViewforms.js @@ -0,0 +1,74 @@ +module.exports = writeViewforms; + +const fs = require('fs'); +const path = require('path'); +const {PROPERTY_NAMES_TO_IGNORE} = require('./constants.json'); + +const viewTargets = { + create: require('../target/viewCreate.json'), + item: require('../target/viewItem.json'), + list: require('../target/viewList.json') +}; +const viewPropertiesDir = path.join(__dirname, '..', 'target', 'viewProperty'); + +async function writeViewforms(metadataStructure, destination) { + const writingPromises = []; + for (const dataClassName of Object.keys(metadataStructure)) { + writingPromises.push(writeClassViewforms(metadataStructure[dataClassName], dataClassName, 'create', viewTargets, destination)); + writingPromises.push(writeClassViewforms(metadataStructure[dataClassName], dataClassName, 'item', viewTargets, destination)); + writingPromises.push(writeClassViewforms(metadataStructure[dataClassName], dataClassName, 'list', viewTargets, destination)); + } + + return await Promise.all(writingPromises); +} + +async function writeClassViewforms(dataClass, name, type, viewTargets, destination) { + const classViewsPath = path.join(destination, name); + if (!fs.existsSync(classViewsPath)) + fs.mkdirSync(classViewsPath); + const newView = JSON.parse(JSON.stringify(viewTargets[type])); + newView['name'] = name; + newView['caption'] = `${name[0].toUpperCase()}${name.slice(1)}`; + const viewPath = path.join(classViewsPath, `${type}.json`); + + const writingPromises = []; + + let propOrderNumber = 10; + + for (const propertyName of Object.keys(dataClass)) { + if (PROPERTY_NAMES_TO_IGNORE.includes(propertyName)) + continue; + const refClass = dataClass[propertyName]['_deducedClass']? + dataClass[propertyName]['_deducedClass'] + : propertyName; + const propertyType = dataClass[propertyName]._dataType; + const property = Object.assign({}, require(path.join(viewPropertiesDir, `${propertyType}.json`))); + if (propertyType === 'object') { + if (!fs.existsSync(path.join(destination, refClass, 'create.json'))) + writingPromises.push(writeClassViewforms(dataClass[propertyName], refClass, 'create', viewTargets, destination)); + if (!fs.existsSync(path.join(destination, refClass, 'item.json'))) + writingPromises.push(writeClassViewforms(dataClass[propertyName], refClass, 'item', viewTargets, destination)); + if (!fs.existsSync(path.join(destination, refClass, 'list.json'))) + writingPromises.push(writeClassViewforms(dataClass[propertyName], refClass, 'list', viewTargets, destination)); + } + + property['property'] = propertyName; + property['caption'] = `${propertyName[0].toUpperCase()}${propertyName.slice(1)}`; + property['orderNumber'] = propOrderNumber; + propOrderNumber += 10; + + if ( + (type === 'create') + || (type === 'item') + ) + newView.tabs[0].fullFields.push(property); + else if (type === 'list') + newView.columns.push(property); + } + + writingPromises.push(new Promise((resolve, reject) => { + fs.writeFile(viewPath, JSON.stringify(newView, null, 2), () => resolve(viewPath)) + })); + + return await Promise.all(writingPromises); +} diff --git a/backend/xls/objectStructure/expandObjectStructure.js b/backend/xls/objectStructure/expandObjectStructure.js new file mode 100644 index 0000000..949e7a5 --- /dev/null +++ b/backend/xls/objectStructure/expandObjectStructure.js @@ -0,0 +1,56 @@ +module.exports = expandObjectStructure; + +const uuid = require('uuid'); +const {PROPERTY_NAMES_TO_IGNORE} = require('../metadataStructure/constants.json'); + +function expandObjectStructure(objectStructure, metadataStructure) { + const expandedStructure = {}; + for (const className of Object.keys(objectStructure)) { + const objects = objectStructure[className]; + for (const object of objects) { + const expandedObject = expandObject(object, className, metadataStructure[className]); + for (const innerClassName of Object.keys(expandedObject)) { + const innerObjects = expandedObject[innerClassName]; + if (!expandedStructure[innerClassName]) + expandedStructure[innerClassName] = []; + expandedStructure[innerClassName] = expandedStructure[innerClassName].concat(innerObjects); + } + } + } + return expandedStructure; +} + +function expandObject(object, className, metadata) { + const expandedObject = {}; + expandedObject[className] = []; + const newObject = {}; + newObject['guid'] = uuid.v4(); + for (const propertyName of Object.keys(object)) { + if (PROPERTY_NAMES_TO_IGNORE.includes(propertyName)) + continue; + const property = object[propertyName]; + const propertyMetadata = metadata[propertyName]; + const type = propertyMetadata._dataType; + if (type === 'object') { + if (!property) { + newObject[propertyName] = null; + continue; + } + const innerObject = {}; + let innerClassName = propertyName; + if (propertyMetadata._deducedClass) { + innerClassName = propertyMetadata._deducedClass; + innerObject[propertyMetadata._value] = property; + } + innerObject['guid'] = uuid.v4(); + if (!expandedObject[innerClassName]) + expandedObject[innerClassName] = []; + expandedObject[innerClassName].push(innerObject); + newObject[propertyName] = innerObject['guid']; + } else { + newObject[propertyName] = property; + } + } + expandedObject[className].push(newObject); + return expandedObject; +} diff --git a/backend/xls/objectStructure/getObjects.js b/backend/xls/objectStructure/getObjects.js new file mode 100644 index 0000000..c29ee4a --- /dev/null +++ b/backend/xls/objectStructure/getObjects.js @@ -0,0 +1,17 @@ +module.exports = getObjects; + +const xls = require('../dbtypes/xls'); + +async function getObjects(file) { + const xlsData = xls(file); + const data = {}; + + for (const sheetName of Object.keys(xlsData)) { + const sheet = xlsData[sheetName]; + data[sheetName] = []; + for (const xlsObject of sheet.data) + data[sheetName].push(Object.assign({}, xlsObject)); + } + + return data; // JSON.stringify(data, null, 2); +} diff --git a/backend/xls/objectStructure/index.js b/backend/xls/objectStructure/index.js new file mode 100644 index 0000000..e637d9e --- /dev/null +++ b/backend/xls/objectStructure/index.js @@ -0,0 +1,6 @@ +module.exports = { + getObjects: require('./getObjects'), + prepareObjectStructure: require('./prepareObjectStructure'), + expandObjectStructure: require('./expandObjectStructure'), + writeObjectStructure: require('./writeObjectStructure') +}; diff --git a/backend/xls/objectStructure/prepareObjectStructure.js b/backend/xls/objectStructure/prepareObjectStructure.js new file mode 100644 index 0000000..b59032f --- /dev/null +++ b/backend/xls/objectStructure/prepareObjectStructure.js @@ -0,0 +1,33 @@ +module.exports = prepareObjectStructure; + +const miscUtils = require('../util/misc'); +const translit = new (require('cyrillic-to-translit-js'))(); + +function prepareObjectStructure(rawObjectStructure) { + return convertRawObject(rawObjectStructure); +} + +function convertRawObject(object) { + let newObject; + if (!Array.isArray(object)) { + newObject = {}; + for (const propertyName of Object.keys(object)) { + const property = object[propertyName]; + const newPropertyName = translit.transform(miscUtils.consolidateName(propertyName)); + if (property && typeof property === 'object') { + newObject[newPropertyName] = convertRawObject(object[propertyName]); + } else { + newObject[newPropertyName] = object[propertyName]; + } + } + } else { + newObject = []; + for (const property of object) { + if (property && typeof property === 'object') + newObject.push(convertRawObject(property)); + else + newObject.push(property); + } + } + return newObject; +} diff --git a/backend/xls/objectStructure/writeObjectStructure.js b/backend/xls/objectStructure/writeObjectStructure.js new file mode 100644 index 0000000..3381acc --- /dev/null +++ b/backend/xls/objectStructure/writeObjectStructure.js @@ -0,0 +1,27 @@ +module.exports = writeObjectStructure; + +const fs = require('fs'); +const path = require('path'); + +const dataTarget = require('../target/data.json'); + +async function writeObjectStructure(objectStructure, namespace, destination) { + const writingPromises = []; + for (const className of Object.keys(objectStructure)) { + const objects = objectStructure[className]; + for (const object of objects) { + const objectPath = path.join(destination, `${className}@${namespace}@${object.guid}.json`); + writingPromises.push(writeObjectData(object, className, namespace, dataTarget, objectPath)); + } + } + + return await Promise.all(writingPromises); +} + +async function writeObjectData(objectData, className, namespace, target, destination) { + const dataToWrite = Object.assign({}, target, objectData); + dataToWrite['_class'] = `${className}@${namespace}`; + return new Promise((resolve, reject) => { + fs.writeFile(destination, JSON.stringify(objectData, null, 2), () => resolve(destination)) + }); +} diff --git a/backend/xls/target/data.json b/backend/xls/target/data.json new file mode 100644 index 0000000..2da89de --- /dev/null +++ b/backend/xls/target/data.json @@ -0,0 +1,6 @@ +{ + "guid": "", + "_class": "", + "_classVer": "", + "___workflowStatus": {} +} diff --git a/backend/xls/target/deploy.json b/backend/xls/target/deploy.json new file mode 100644 index 0000000..d54d1ac --- /dev/null +++ b/backend/xls/target/deploy.json @@ -0,0 +1,17 @@ +{ + "namespace": "", + "parametrised": true, + "deployer": "built-in", + "globals": { + "staticOptions": { + "maxAge": 3600000 + }, + "lang": "en", + "moduleTitles": {}, + "explicitTopMenu": [], + "plugins": {}, + "jobs": {} + }, + + "modules": {} +} \ No newline at end of file diff --git a/backend/xls/target/metadata.class.json b/backend/xls/target/metadata.class.json new file mode 100644 index 0000000..747803e --- /dev/null +++ b/backend/xls/target/metadata.class.json @@ -0,0 +1,48 @@ +{ + "name": "", + "namespace": "", + "caption": "", + "key": [ + "guid" + ], + "semantic": "", + "version": "", + "ancestor": "", + "container": "", + "creationTracker": null, + "changeTracker": null, + "journaling": false, + "isStruct": false, + "compositeIndexes": [], + "properties": [ + { + "name": "guid", + "caption": "ID", + "type": 12, + "orderNumber": 10, + "size": 24, + "decimals": 0, + "allowedFileTypes": "", + "maxFileCount": 0, + "hint": "", + "semantic": "", + "refClass": "", + "itemsClass": "", + "backRef": "", + "backColl": "", + "defaultValue": null, + "binding": null, + "selConditions": null, + "selSorting": null, + "selectionProvider": null, + "formula": null, + "eagerLoading": false, + "readonly": true, + "indexed": true, + "nullable": false, + "unique": true, + "autoassigned": true, + "indexSearch": false + } + ] +} \ No newline at end of file diff --git a/backend/xls/target/metadataProperty/object.json b/backend/xls/target/metadataProperty/object.json new file mode 100644 index 0000000..9ea6a2d --- /dev/null +++ b/backend/xls/target/metadataProperty/object.json @@ -0,0 +1,29 @@ +{ + "name": "", + "caption": "", + "type": 13, + "mode": 0, + "size": null, + "decimals": 0, + "allowedFileTypes": "", + "maxFileCount": 0, + "hint": "", + "semantic": "", + "refClass": "", + "itemsClass": "", + "backRef": "", + "backColl": "", + "defaultValue": "", + "binding": null, + "selConditions": [], + "selSorting": null, + "selectionProvider": null, + "formula": null, + "eagerLoading": false, + "readonly": false, + "indexed": true, + "nullable": true, + "unique": false, + "autoassigned": false, + "indexSearch": false +} diff --git a/backend/xls/target/metadataProperty/string.json b/backend/xls/target/metadataProperty/string.json new file mode 100644 index 0000000..b794d6b --- /dev/null +++ b/backend/xls/target/metadataProperty/string.json @@ -0,0 +1,29 @@ +{ + "name": "", + "caption": "", + "type": 0, + "mode": 0, + "size": null, + "decimals": 0, + "allowedFileTypes": "", + "maxFileCount": 0, + "hint": "", + "semantic": "", + "refClass": "", + "itemsClass": "", + "backRef": "", + "backColl": "", + "defaultValue": "", + "binding": null, + "selConditions": null, + "selSorting": null, + "selectionProvider": null, + "formula": null, + "eagerLoading": false, + "readonly": false, + "indexed": false, + "nullable": true, + "unique": false, + "autoassigned": false, + "indexSearch": false +} \ No newline at end of file diff --git a/backend/xls/target/navigationNode.json b/backend/xls/target/navigationNode.json new file mode 100644 index 0000000..5741250 --- /dev/null +++ b/backend/xls/target/navigationNode.json @@ -0,0 +1,15 @@ +{ + "code": "", + "caption": "", + "type": 1, + "orderNumber": 0, + "title": "", + "hint": "", + "classname": "", + "container": "", + "collection": "", + "url": "", + "conditions": [], + "sorting": [], + "pathChains": [] +} \ No newline at end of file diff --git a/backend/xls/target/navigationSection.section.json b/backend/xls/target/navigationSection.section.json new file mode 100644 index 0000000..c77e6d5 --- /dev/null +++ b/backend/xls/target/navigationSection.section.json @@ -0,0 +1,7 @@ +{ + "name": "", + "caption": "", + "orderNumber": 10, + "mode": 0, + "tags": "" +} \ No newline at end of file diff --git a/backend/xls/target/viewCreate.json b/backend/xls/target/viewCreate.json new file mode 100644 index 0000000..9811e7e --- /dev/null +++ b/backend/xls/target/viewCreate.json @@ -0,0 +1,31 @@ +{ + "tabs": [ + { + "caption": "", + "fullFields": [], + "shortFields": [] + } + ], + "commands": [ + { + "id": "SAVE", + "caption": "Create", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + }, + { + "id": "SAVEANDCLOSE", + "caption": "Create and close", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + } + ] +} \ No newline at end of file diff --git a/backend/xls/target/viewItem.json b/backend/xls/target/viewItem.json new file mode 100644 index 0000000..5caca60 --- /dev/null +++ b/backend/xls/target/viewItem.json @@ -0,0 +1,31 @@ +{ + "tabs": [ + { + "caption": "", + "fullFields": [], + "shortFields": [] + } + ], + "commands": [ + { + "id": "SAVE", + "caption": "Save", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + }, + { + "id": "SAVEANDCLOSE", + "caption": "Save and close", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + } + ] +} \ No newline at end of file diff --git a/backend/xls/target/viewList.json b/backend/xls/target/viewList.json new file mode 100644 index 0000000..0a45541 --- /dev/null +++ b/backend/xls/target/viewList.json @@ -0,0 +1,35 @@ +{ + "columns": [], + "commands": [ + { + "id": "CREATE", + "caption": "Create", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + }, + { + "id": "EDIT", + "caption": "Edit", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": true, + "signBefore": false, + "signAfter": false, + "isBulk": false + }, + { + "id": "DELETE", + "caption": "Delete", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": true, + "signBefore": false, + "signAfter": false, + "isBulk": false + } + ] +} \ No newline at end of file diff --git a/backend/xls/target/viewProperty/object.json b/backend/xls/target/viewProperty/object.json new file mode 100644 index 0000000..dec5d72 --- /dev/null +++ b/backend/xls/target/viewProperty/object.json @@ -0,0 +1,52 @@ +{ + "property": "", + "caption": "", + "type": 2, + "orderNumber": 0, + "hint": "", + "size": null, + "maskName": "", + "mask": "", + "mode": 0, + "commands": [ + { + "id": "CREATE", + "caption": "Create", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + }, + { + "id": "EDIT", + "caption": "Edit", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": true, + "signBefore": false, + "signAfter": false, + "isBulk": false + }, + { + "id": "SELECT", + "caption": "Add", + "visibilityCondition": null, + "enableCondition": null, + "needSelectedItem": false, + "signBefore": false, + "signAfter": false, + "isBulk": false + } + ], + "required": true, + "readonly": false, + "sorted": false, + "selectionPaginated": false, + "visibility": "", + "enablement": "", + "obligation": "", + "historyDisplayMode": 0, + "tags": "" +} diff --git a/backend/xls/target/viewProperty/string.json b/backend/xls/target/viewProperty/string.json new file mode 100644 index 0000000..c2a5990 --- /dev/null +++ b/backend/xls/target/viewProperty/string.json @@ -0,0 +1,21 @@ +{ + "property": "", + "caption": "", + "type": 1, + "orderNumber": 0, + "hint": "", + "size": null, + "maskName": "", + "mask": "", + "mode": 0, + "commands": null, + "required": true, + "readonly": false, + "sorted": false, + "selectionPaginated": false, + "visibility": "", + "enablement": "", + "obligation": "", + "historyDisplayMode": 0, + "tags": "" +} diff --git a/backend/xls/util/misc.js b/backend/xls/util/misc.js new file mode 100644 index 0000000..115884f --- /dev/null +++ b/backend/xls/util/misc.js @@ -0,0 +1,46 @@ +module.exports = { + consolidateName, + rmRecursively +}; + +const path = require('path'); +const fs = require('fs'); + +function consolidateName(src) { +return src.split(' ') + .map((part, i) => + i === 0 ? + part + : `${part[0].toUpperCase()}${part.slice(1)}` + ).join(''); +} + +function rmRecursively(filePath) { + return new Promise((resolve, reject) => { + fs.stat(filePath, {}, (err, stat) => { + if (err) + return reject(err); + + if (stat.isDirectory()) { + + fs.readdir(filePath, {}, (err, innerFiles) => { + if (err) + return reject(err); + + const rmPromises = []; + for (const innerFile of innerFiles) { + const nextPath = path.join(filePath, innerFile); + rmPromises.push(rmRecursively(nextPath)); + } + Promise.all(rmPromises) + .catch(err => reject(err)) + .then(() => { + fs.rmdir(filePath, resolve); + }); + }); + + } else + fs.unlink(filePath, resolve); + }); + }); +} diff --git a/controllers/dbToApp.js b/controllers/dbToApp.js new file mode 100644 index 0000000..979149e --- /dev/null +++ b/controllers/dbToApp.js @@ -0,0 +1,26 @@ +module.exports = serve; + +const appMaker = require('../backend/xls/appMaker'); + +function serve(req, res) { + try { + const chunks = []; + return new Promise((resolve) => { + req.on('data', (chunk) => { + chunks.push(chunk); + }); + req.on('end', async () => { + const source = Buffer.concat(chunks); + const appName = 'generated-ion-app'; + const app = new appMaker(appName); + const zip = await (app).fromXls(source); + await app.clean(); + res.send(zip); + return resolve(zip); + }); + }); + } catch (err) { + console.error(err); + res.status(500).send(err.toString()); + } +} diff --git a/controllers/index.js b/controllers/index.js index 84b453a..5902d4a 100644 --- a/controllers/index.js +++ b/controllers/index.js @@ -3,5 +3,6 @@ module.exports = { 'create': require('./create'), 'get': require('./get'), - 'update': require('./update') + 'update': require('./update'), + 'dbToApp': require('./dbToApp') }; \ No newline at end of file diff --git a/package.json b/package.json index 656e97a..a7330a0 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,11 @@ "express": "^4.16.2", "merge": "^1.2.0", "extend": "^3.0.1", - "request": "*" + "request": "*", + "archiver": "^5.1.0", + "cyrillic-to-translit-js": "^3.1.0", + "uuid": "^8.3.1", + "xlsx": "^0.16.8" }, "devDependencies": { "gulp": "^4.0.0", diff --git a/standalone b/standalone index 1f73e3f..31ad162 100644 --- a/standalone +++ b/standalone @@ -30,12 +30,14 @@ app.use(bodyParser.raw({limit})); app.post('/api/create', controllers.create); app.post('/api/get', controllers.get); app.post('/api/update', controllers.update); +app.put('/api/xls', controllers.dbToApp); app.locals.standalone = { api: { create: 'api/create', get: 'api/get', - update: 'api/update' + update: 'api/update', + dbToApp: 'api/xls' } }; app.get('/' + defaultPage, (request, response) => { diff --git a/themes/portal/static/css/studio.css b/themes/portal/static/css/studio.css index 27f36fa..645cfd1 100644 --- a/themes/portal/static/css/studio.css +++ b/themes/portal/static/css/studio.css @@ -789,7 +789,8 @@ display: block; } .studio-tabs .tab-add, -.studio-tabs .tab-import { +.studio-tabs .tab-import, +.studio-tabs .tab-xls { top: 8px; left: 2px; position: relative; @@ -799,12 +800,16 @@ color: #666; } .studio-tabs .tab-add:hover, -.studio-tabs .tab-import:hover { +.studio-tabs .tab-import:hover, +.studio-tabs .tab-xls:hover { color: #000; } .studio-tabs .tab-add { margin-right: 15px; } +.studio-tabs .tab-import { + margin-right: 15px; +} .studio-logo { display: block; float: left; diff --git a/themes/portal/static/js/studio/components/tabs.js b/themes/portal/static/js/studio/components/tabs.js index 20a4684..8228f6a 100644 --- a/themes/portal/static/js/studio/components/tabs.js +++ b/themes/portal/static/js/studio/components/tabs.js @@ -6,6 +6,8 @@ Studio.Tabs = function ($container, studio) { this.$list = $container.find('.tab-list'); this.$add = $container.find('.tab-add'); this.$import = $container.find('.tab-import'); + this.$xls = $container.find('.tab-xls'); + this.$xlsFile = this.$xls.find('#xlsFile'); this.init(); }; @@ -17,6 +19,7 @@ $.extend(Studio.Tabs.prototype, { this.$list.on('dblclick', '.active.tab', this.onUpdateTab.bind(this)); this.$add.click(this.onAdd.bind(this)); this.$import.click(this.onImport.bind(this)); + this.$xls.on('input', this.onXls.bind(this)); }, initListeners: function () { @@ -89,6 +92,17 @@ $.extend(Studio.Tabs.prototype, { this.studio.importExternalAppForm.show(); }, + onXls: async function () { + const xlsFile = await this.$xlsFile[0].files[0].arrayBuffer(); + this.$xlsFile[0].value = ''; + const app = await fetch('/api/xls', { + method: 'PUT', + body: xlsFile + }).then(response => response.arrayBuffer()); + this.studio.toggleLoader(false); + (new Studio.AppImport(app, this.studio)).execute(); + }, + restore: function () { this.studio.apps.forEach(this.createTab, this); }, diff --git a/themes/portal/templates/parts/tabs.ejs b/themes/portal/templates/parts/tabs.ejs index a93b684..3b52270 100644 --- a/themes/portal/templates/parts/tabs.ejs +++ b/themes/portal/templates/parts/tabs.ejs @@ -11,4 +11,8 @@ <% } %> +
+ + +
\ No newline at end of file From 6517a236efd23b3684dd65554c1197b9e521b249 Mon Sep 17 00:00:00 2001 From: sovereign Date: Thu, 20 May 2021 13:01:54 +1000 Subject: [PATCH 2/4] =?UTF-8?q?IONEXAMPLE-196=20=D1=84=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D1=82=D1=80=20=D1=82=D0=B5=D0=BB=D0=B5=D1=84=D0=BE=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../xls/metadataStructure/filters/phone.json | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/backend/xls/metadataStructure/filters/phone.json b/backend/xls/metadataStructure/filters/phone.json index 7a56009..8b08c45 100644 --- a/backend/xls/metadataStructure/filters/phone.json +++ b/backend/xls/metadataStructure/filters/phone.json @@ -11,19 +11,28 @@ "\\+999999999999": { "name": "phone", "structure": { - "number": ":this" - } + "number": { + "_dataType": "string" + } + }, + "_value": "number" }, "\\+9999999999999": { "name": "phone", "structure": { - "number": ":this" - } + "number": { + "_dataType": "string" + } + }, + "_value": "number" }, "\\+\\d+?-999-999-9999": { "name": "phone", "structure": { - "number": ":this" - } + "number": { + "_dataType": "string" + } + }, + "_value": "number" } } \ No newline at end of file From 53476d86e86e37f01c74cbe9526a96a892ba1c3c Mon Sep 17 00:00:00 2001 From: sovereign Date: Thu, 20 May 2021 13:34:45 +1000 Subject: [PATCH 3/4] =?UTF-8?q?IONEXAMPLE-196=20xls=20=D0=B2=20=D0=B2?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=D0=B5=20=D1=81=20=D1=84?= =?UTF-8?q?=D1=80=D0=B5=D0=B9=D0=BC=D0=B2=D0=BE=D1=80=D0=BA=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy.json | 14 ++++++++++++++ package.json | 3 ++- services/dbToApp.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 services/dbToApp.js diff --git a/deploy.json b/deploy.json index ededf1f..2e0334a 100644 --- a/deploy.json +++ b/deploy.json @@ -71,6 +71,20 @@ } } } + }, + "rest": { + "globals": { + "authMode": { + "dbToMetadata": "none" + }, + "di": { + "dbToMetadata": { + "module": "applications/studio/services/dbToApp", + "options": { + } + } + } + } } } } diff --git a/package.json b/package.json index a7330a0..c9cb0d6 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ }, "private": true, "ionModulesDependencies": { - "portal": "1.4.1" + "portal": "1.4.1", + "rest": "*" }, "ionMetaDependencies": {}, "dependencies": { diff --git a/services/dbToApp.js b/services/dbToApp.js new file mode 100644 index 0000000..00144c9 --- /dev/null +++ b/services/dbToApp.js @@ -0,0 +1,32 @@ +const Service = require('modules/rest/lib/interfaces/Service'); +const appMaker = require('../backend/xls/appMaker'); + +function DbToMetadata(options) { + this._route = function(router) { + /** + * @param {Request} req + * @returns {Promise} + * @private + */ + this.addHandler(router, '/xls', 'PUT', (req) => { + const chunks = []; + return new Promise((resolve) => { + req.on('data', (chunk) => { + chunks.push(chunk); + }); + req.on('end', async () => { + const source = Buffer.concat(chunks); + const appName = 'generated-ion-app'; + const app = new appMaker(appName); + const zip = await (app).fromXls(source); + await app.clean(); + resolve(zip); + }); + }); + }); + }; +} + +DbToMetadata.prototype = new Service(); + +module.exports = DbToMetadata; From 8c2621e3f588297f2957c404a22fb957c18c2828 Mon Sep 17 00:00:00 2001 From: sovereign Date: Thu, 20 May 2021 14:00:09 +1000 Subject: [PATCH 4/4] =?UTF-8?q?IONEXAMPLE-196=20=D1=84=D0=B8=D0=BA=D1=81?= =?UTF-8?q?=20=D0=B7=D0=B0=D0=BF=D1=80=D0=BE=D1=81=D0=B0=20xls=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=D0=B0=20=D1=81=20?= =?UTF-8?q?=D1=84=D1=80=D0=B5=D0=B9=D0=BC=D0=B2=D0=BE=D1=80=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controllers/dbToApp.js | 22 ++++++++++++------- services/dbToApp.js | 19 ++++++++++------ .../static/js/studio/components/tabs.js | 8 +++++-- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/controllers/dbToApp.js b/controllers/dbToApp.js index 979149e..e60e150 100644 --- a/controllers/dbToApp.js +++ b/controllers/dbToApp.js @@ -5,18 +5,24 @@ const appMaker = require('../backend/xls/appMaker'); function serve(req, res) { try { const chunks = []; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { req.on('data', (chunk) => { chunks.push(chunk); }); req.on('end', async () => { - const source = Buffer.concat(chunks); - const appName = 'generated-ion-app'; - const app = new appMaker(appName); - const zip = await (app).fromXls(source); - await app.clean(); - res.send(zip); - return resolve(zip); + try { + const source = Buffer.concat(chunks); + const appName = 'generated-ion-app'; + const app = new appMaker(appName); + const zip = await (app).fromXls(source); + await app.clean(); + res.send(zip); + return resolve(zip); + } catch (err) { + console.error(err); + res.status(500).send(err.toString()); + return reject('conversion failed'); + } }); }); } catch (err) { diff --git a/services/dbToApp.js b/services/dbToApp.js index 00144c9..089b9d3 100644 --- a/services/dbToApp.js +++ b/services/dbToApp.js @@ -10,17 +10,22 @@ function DbToMetadata(options) { */ this.addHandler(router, '/xls', 'PUT', (req) => { const chunks = []; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { req.on('data', (chunk) => { chunks.push(chunk); }); req.on('end', async () => { - const source = Buffer.concat(chunks); - const appName = 'generated-ion-app'; - const app = new appMaker(appName); - const zip = await (app).fromXls(source); - await app.clean(); - resolve(zip); + try { + const source = Buffer.concat(chunks); + const appName = 'generated-ion-app'; + const app = new appMaker(appName); + const zip = await (app).fromXls(source); + await app.clean(); + return resolve(zip); + } catch (err) { + console.error(err); + return reject('conversion failed'); + } }); }); }); diff --git a/themes/portal/static/js/studio/components/tabs.js b/themes/portal/static/js/studio/components/tabs.js index 8228f6a..9b0e12d 100644 --- a/themes/portal/static/js/studio/components/tabs.js +++ b/themes/portal/static/js/studio/components/tabs.js @@ -95,10 +95,14 @@ $.extend(Studio.Tabs.prototype, { onXls: async function () { const xlsFile = await this.$xlsFile[0].files[0].arrayBuffer(); this.$xlsFile[0].value = ''; - const app = await fetch('/api/xls', { + let app; + const requestOptions = { method: 'PUT', body: xlsFile - }).then(response => response.arrayBuffer()); + }; + app = await fetch('/api/xls', requestOptions) + .catch(err => fetch('/rest/dbToMetadata/xls', requestOptions)) + .then(response => response.arrayBuffer()); this.studio.toggleLoader(false); (new Studio.AppImport(app, this.studio)).execute(); },