From af1068f9039f2201277e08f8f298f2fc62365b1b Mon Sep 17 00:00:00 2001 From: Tomer Salton Date: Tue, 8 Jan 2019 15:44:28 +0000 Subject: [PATCH 01/89] feat: cli add lb4 relation command Solution for issue #1359: loopback-next supports lb4 relation command. --- packages/cli/generators/relation/index.js | 26 +++++++++++++++++++ packages/cli/lib/cli.js | 6 ++++- .../test/integration/cli/cli.integration.js | 16 +++++++----- .../generators/relation.integration.js | 1 + 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 packages/cli/generators/relation/index.js create mode 100644 packages/cli/test/integration/generators/relation.integration.js diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js new file mode 100644 index 000000000000..d5e9b75d6aff --- /dev/null +++ b/packages/cli/generators/relation/index.js @@ -0,0 +1,26 @@ +'use strict'; + +const ArtifactGenerator = require('../../lib/artifact-generator'); +const utils = require('../../lib/utils'); +const path = require('path'); + +module.exports = class RelationGenerator extends ArtifactGenerator { + + constructor(args, opts) { + super(args, opts); + } + + _setupGenerator() { + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + outDir: utils.sourceRootDir, + }; + + return super._setupGenerator(); + } + + helloWorld() { + this.log("Hello World"); + } +} diff --git a/packages/cli/lib/cli.js b/packages/cli/lib/cli.js index 9925d79c4a8c..3f7e36c00cce 100644 --- a/packages/cli/lib/cli.js +++ b/packages/cli/lib/cli.js @@ -32,7 +32,7 @@ function runCommand(env, opts, log, dryRun) { } debug('invoking generator', args); // `yo` is adding flags converted to CamelCase - const options = camelCaseKeys(opts, {exclude: ['--', /^\w$/, 'argv']}); + const options = camelCaseKeys(opts, { exclude: ['--', /^\w$/, 'argv'] }); Object.assign(options, opts); debug('env.run %j %j', args, options); if (!dryRun) { @@ -79,6 +79,10 @@ function setupGenerators() { path.join(__dirname, '../generators/openapi'), PREFIX + 'openapi', ); + env.register( + path.join(__dirname, '../generators/relation'), + PREFIX + 'relation', + ); return env; } diff --git a/packages/cli/test/integration/cli/cli.integration.js b/packages/cli/test/integration/cli/cli.integration.js index d0272d17c61b..eb81cfd74870 100644 --- a/packages/cli/test/integration/cli/cli.integration.js +++ b/packages/cli/test/integration/cli/cli.integration.js @@ -11,7 +11,7 @@ const main = require('../../../lib/cli'); function getLog(buffer) { buffer = buffer || []; - return function(format, ...params) { + return function (format, ...params) { buffer.push(util.format(format, ...params)); return buffer; }; @@ -20,17 +20,18 @@ function getLog(buffer) { describe('cli', () => { it('lists available commands', () => { const entries = []; - main({commands: true}, getLog(entries)); + main({ commands: true }, getLog(entries)); expect(entries).to.eql([ 'Available commands: ', ' lb4 app\n lb4 extension\n lb4 controller\n lb4 datasource\n ' + - 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n lb4 openapi', + 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n ' + + 'lb4 openapi\n lb4 relation', ]); }); it('lists versions', () => { const entries = []; - main({version: true}, getLog(entries)); + main({ version: true }, getLog(entries)); const logs = entries.join(''); expect(logs).to.match(/@loopback\/cli version\:/); expect(logs).to.match(/@loopback\/\* dependencies:/); @@ -38,17 +39,18 @@ describe('cli', () => { it('prints commands with --help', () => { const entries = []; - main({help: true, _: []}, getLog(entries), true); + main({ help: true, _: [] }, getLog(entries), true); expect(entries).to.containEql('Available commands: '); expect(entries).to.containEql( ' lb4 app\n lb4 extension\n lb4 controller\n lb4 datasource\n ' + - 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n lb4 openapi', + 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n ' + + 'lb4 openapi\n lb4 relation', ); }); it('does not print commands with --help for a given command', () => { const entries = []; - main({help: true, _: ['app']}, getLog(entries), true); + main({ help: true, _: ['app'] }, getLog(entries), true); expect(entries).to.not.containEql('Available commands: '); }); }); diff --git a/packages/cli/test/integration/generators/relation.integration.js b/packages/cli/test/integration/generators/relation.integration.js new file mode 100644 index 000000000000..dab2dbd14f8b --- /dev/null +++ b/packages/cli/test/integration/generators/relation.integration.js @@ -0,0 +1 @@ +('use strict'); From 0a4f195686e81fd515cbcc8798735c7324e68264 Mon Sep 17 00:00:00 2001 From: "s.itzhak" Date: Mon, 21 Jan 2019 16:18:05 +0200 Subject: [PATCH 02/89] feat(cli): add lb4 relation cli user input solution for issue #1359:loopback-next support lb4 relation command adding cli relation command --- packages/cli/generators/relation/index.js | 226 +++++++++++++++++++++- 1 file changed, 221 insertions(+), 5 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index d5e9b75d6aff..6666fadf8e1c 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -4,11 +4,74 @@ const ArtifactGenerator = require('../../lib/artifact-generator'); const utils = require('../../lib/utils'); const path = require('path'); +const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; +const PROMPT_MESSAGE__SOURCE_MODEL = 'Please select source model'; +const PROMPT_MESSAGE__TARGET__MODEL = 'Please select target model'; +const availableRelationsBaseClasses = ['belongsTo', 'hasMany', 'hasOne']; module.exports = class RelationGenerator extends ArtifactGenerator { - constructor(args, opts) { super(args, opts); } + async _getModelIdProperty(modelName) { + let fileContent = ''; + let modelFile = path.join( + this.artifactInfo.modelDir, + utils.getModelFileName(modelName), + ); + try { + fileContent = this.fs.read(modelFile, {}); + } catch (err) { + //debug(`${ERROR_READING_FILE} ${modelFile}: ${err.message}`); + return this.exit(err); + } + + return tsquery.getIdFromModel(fileContent); + } + async _scaffold() { + if (this.shouldExit()) return false; + + if (this.options.name) { + this.artifactInfo.className = utils.toClassName(this.options.name); + this.artifactInfo.outFile = utils.getRepositoryFileName( + this.options.name, + ); + + // make sure the name supplied from cmd line is only used once + delete this.options.name; + } else { + this.artifactInfo.className = utils.toClassName( + this.artifactInfo.modelName, + ); + + this.artifactInfo.outFile = utils.getRepositoryFileName( + this.artifactInfo.modelName, + ); + + this.artifactInfo.indexesToBeUpdated.push({ + dir: this.artifactInfo.outDir, + file: this.artifactInfo.outFile, + }); + } + + const source = this.templatePath( + path.join( + utils.sourceRootDir, + utils.repositoriesDir, + this.artifactInfo.defaultTemplate, + ), + ); + + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), + ); + + if (debug.enabled) { + //debug(`artifactInfo: ${inspect(this.artifactInfo)}`); + //debug(`Copying artifact to: ${dest}`); + } + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; + } _setupGenerator() { this.artifactInfo = { @@ -16,11 +79,164 @@ module.exports = class RelationGenerator extends ArtifactGenerator { rootDir: utils.sourceRootDir, outDir: utils.sourceRootDir, }; + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + utils.modelsDir, + ); + } + // Prompt a user for Relation type + async promptRelationBaseClassName() { + this.artifactInfo.relationType = await this.prompt([ + { + type: 'list', + name: 'relationBaseClass', + message: PROMPT_BASE_RELATION_CLASS, + choices: availableRelationsBaseClasses, + when: !this.artifactInfo.availableRelationsBaseClasses, + validate: utils.validateClassName, + }, + ]); + return this.artifactInfo.relationType; + } + + //Get model list for source model + async promptSourceModels() { + if (this.shouldExit()) return false; + + let modelList; + try { + //debug(`model list dir ${this.artifactInfo.modelDir}`); + modelList = await utils.getArtifactList( + this.artifactInfo.modelDir, + 'model', + ); + } catch (err) { + return this.exit(err); + } + + if (this.options.model) { + // debug(`Model name received from command line: ${this.options.model}`); + + this.options.model = utils.toClassName(this.options.model); + // assign the model name from the command line only if it is valid + if ( + modelList && + modelList.length > 0 && + modelList.includes(this.options.model) + ) { + Object.assign(this.artifactInfo, {modelNameList: [this.options.model]}); + } else { + modelList = []; + } + } + if (modelList.length === 0) { + return this.exit( + new Error( + `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. + ${chalk.yellow( + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, + ), + ); + } + + // Prompt a user for source model + return (this.artifactInfo.sourceModel = await this.prompt([ + { + type: 'list', + name: 'modelNameList', + message: PROMPT_MESSAGE__SOURCE_MODEL, + choices: modelList, + when: this.artifactInfo.modelNameList === undefined, + }, + ])); + } + + //Get model list for target model + async promptTargetModels() { + if (this.shouldExit()) return false; + + let modelList; + try { + //debug(`model list dir ${this.artifactInfo.modelDir}`); + modelList = await utils.getArtifactList( + this.artifactInfo.modelDir, + 'model', + ); + } catch (err) { + return this.exit(err); + } + + if (this.options.model) { + // debug(`Model name received from command line: ${this.options.model}`); + + this.options.model = utils.toClassName(this.options.model); + // assign the model name from the command line only if it is valid + if ( + modelList && + modelList.length > 0 && + modelList.includes(this.options.model) + ) { + Object.assign(this.artifactInfo, {modelNameList: [this.options.model]}); + } else { + modelList = []; + } + } + if (modelList.length === 0) { + return this.exit( + new Error( + `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. + ${chalk.yellow( + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, + ), + ); + } - return super._setupGenerator(); + // Prompt a user for a target model + return (this.artifactInfo.targetModel = await this.prompt([ + { + type: 'list', + name: 'modelNameList', + message: PROMPT_MESSAGE__TARGET__MODEL, + choices: modelList, + when: this.artifactInfo.modelNameList === undefined, + }, + ])); } + async promptModelId() { + if (this.shouldExit()) return false; + let idProperty; + // Prompt a user for a ID property + this.artifactInfo.id = await this.prompt([ + { + type: 'input', + name: 'propertyName', + message: `Please enter the name of the ID property for ${ + this.artifactInfo.sourceModel.modelNameList + }:`, + default: 'id', + }, + ]); - helloWorld() { - this.log("Hello World"); + // user supplied the id from the command line + if (this.options.id) { + idProperty = this.options.id; + /** make sure it is only used once, in case user selected more + * than one model. + */ + delete this.options.id; + } else { + idProperty = await this._getModelIdProperty( + this.artifactInfo.sourceModel, + ); + if (idProperty === null) { + const answer = await this.prompt(prompts); + idProperty = answer.propertyName; + } + } + this.artifactInfo.idProperty = idProperty; + // Generate this repository + await this._scaffold(); } -} +}; From 3231b29903638c6286850e724f9bb6c487682c15 Mon Sep 17 00:00:00 2001 From: "s.itzhak" Date: Wed, 23 Jan 2019 14:18:31 +0200 Subject: [PATCH 03/89] feat(cli): adding lb4 relation cli model controller and repository files solution for issue #1359:loopback-next support lb4 relation command adding new files for lb4 cli relation command --- .../cli/generators/relation/ modelRelation.js | 262 +++++++++++++++++ .../generators/relation/controllerRelation.js | 153 ++++++++++ .../generators/relation/repositoryRelation.js | 271 ++++++++++++++++++ .../controller-relation-template.ts.ejs | 96 +++++++ 4 files changed, 782 insertions(+) create mode 100644 packages/cli/generators/relation/ modelRelation.js create mode 100644 packages/cli/generators/relation/controllerRelation.js create mode 100644 packages/cli/generators/relation/repositoryRelation.js create mode 100644 packages/cli/generators/relation/templates/controller-relation-template.ts.ejs diff --git a/packages/cli/generators/relation/ modelRelation.js b/packages/cli/generators/relation/ modelRelation.js new file mode 100644 index 000000000000..f18a07f149d1 --- /dev/null +++ b/packages/cli/generators/relation/ modelRelation.js @@ -0,0 +1,262 @@ +"use strict"; +const ArtifactGenerator = require('../../lib/artifact-generator'); +const debug = require('../../lib/debug')('relation-generator'); +const inspect = require('util').inspect; +const path = require('path'); +const chalk = require('chalk'); +const utils = require('../../lib/utils'); +const ast = require('ts-simple-ast'); + +module.exports = class ModelRelation extends ArtifactGenerator { + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + } + + generateRelationModel(sourceModel, targetModel, foreignKey, relationName) { + let modelPath = this.artifactInfo.modelDir; + if ((relationName == "hasMany") || (relationName == "hasOne")) { + this.generateModel(sourceModel, targetModel, relationName, modelPath, foreignKey, undefined); + this.generateModel(targetModel, sourceModel, "belongsTo", modelPath, "id", foreignKey); + } + else { + this.generateModel(sourceModel, targetModel, relationName, modelPath, "id", foreignKey); + } + } + + generateModel(sourceModel, targetModel, relationName, path, foreignKey, sourceForeignKey) { + let project = new ast.Project(); + + const sourceFile = this.addFileToProject(project, path, sourceModel); + if (!this.isClassExist(sourceFile)) { + return; + } + const sourceClass = this.getClassObj(sourceFile); + + const targetFile = this.addFileToProject(project, path, targetModel); + if (!this.isClassExist(targetFile)) { + return; + } + const targetClass = this.getClassObj(targetFile); + let modelProperty; + const lowerCaseTargetClassName = this.getClassNameLowerCase(targetFile) + const newPropertyName = this.getForeignKey(sourceClass, foreignKey) + + + switch (relationName) { + case "hasMany": + if (this.isPropertyExist((sourceClass), (lowerCaseTargetClassName + 's'))) { + // TODO add error to CLI UI + throw new Error(' property ' + lowerCaseTargetClassName + 's exist in the model') + } + if (this.isPropertyExist((targetClass), newPropertyName)) { + // TODO add error to CLI UI + throw new Error('wrong property ' + newPropertyName + ' in the target model') + } + else { + modelProperty = this.getHasMany(targetClass.getName(), newPropertyName); + } + break; + case "hasOne": + if (this.isPropertyExist((sourceClass), (lowerCaseTargetClassName))) { + // TODO add error to CLI UI + throw new Error('property ' + lowerCaseTargetClassName + ' exist in the model') + } + if (this.isPropertyExist((targetClass), newPropertyName)) { + // TODO add error to CLI UI + throw new Error('wrong property ' + newPropertyName + ' in the target model') + } + else { + modelProperty = this.getHasOne(targetClass.getName(), newPropertyName); + } + break; + case "belongsTo": + if (this.isPropertyExist((sourceClass), (this.getForeignKey(targetClass, sourceForeignKey)))) { + // TODO add error to CLI UI + throw new Error('property ' + lowerCaseTargetClassName + 'Id exist in the model') + } + let belongsToKey = this.getForeignKeyBelongsTo(sourceClass, foreignKey); + if (!(this.isPropertyExist((targetClass), belongsToKey))) { + // TODO add error to CLI UI + throw new Error('wrong property ' + belongsToKey + ' in the target model') + } + else { + modelProperty = this.getBelongsTo(targetClass.getName(), belongsToKey, this.getKeyType(targetClass, belongsToKey), this.getForeignKey(targetClass, sourceForeignKey)); + } + break; + } + sourceClass.insertProperty(this.getPropertiesCount(sourceClass), modelProperty); + sourceClass.insertText(this.getPropertyStartPos(sourceClass), "\n") + this.addRequiredImports(sourceFile, targetModel, relationName, targetClass.getName()); + sourceClass.formatText() + sourceFile.save(); + } + addFileToProject(project, path, modelName) { + const fileName = path + "/" + modelName + '.model.ts'; + return project.addExistingSourceFile(fileName); + } + + getClassesCount(fileName) { + return fileName.getClasses().length; + } + + + + getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } + + + isClassExist(fileName) { + return (this.getClassesCount(fileName) == 1); + } + + getPropertiesCount(classObj) { + return classObj.getProperties().length; + } + + getPropertyStartPos(classObj) { + return classObj.getChildSyntaxList().getChildAtIndex(this.getPropertiesCount(classObj) - 1).getPos() + } + + getClassProperties(classObj) { + return classObj.getProperties() + } + + isPropertyExist(classObj, propertyName) { + return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) + } + + getClassNameLowerCase(fileName) { + let name = fileName.getClasses()[0].getNameOrThrow() + return name.charAt(0).toLowerCase() + name.slice(1) + } + + getKey(classObj) { + for (let i = 0; i < this.getPropertiesCount(classObj); i++) { + if (classObj.getProperties()[i].getDecorators()[0].getName() == 'property') { + if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperties().map(x => x.getName()).includes('id')) { + if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperty('id').getInitializer().getText() == 'true') { + return (classObj.getProperties()[i].getName()) + } + } + } + } + throw new Error(' primary Key is missing ') + } + + getForeignKey(classObj, foreignKey) { + let name = classObj.getName() + return (name.charAt(0).toLowerCase() + name.slice(1) + foreignKey.charAt(0).toUpperCase() + foreignKey.slice(1)) + + } + + getForeignKeyBelongsTo(classObj, foreignKey) { + if (foreignKey === undefined) { + return (this.getKey(classObj)) + } + return (foreignKey) + } + + getKeyType(classObj, propertyName) { + return classObj.getProperty(propertyName).getType().getText() + } + + + getHasMany(className, fk) { + let relationProperty = { + decorators: [{ name: "hasMany", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], + name: className.toLocaleLowerCase() + "s", + type: className + "[]", + } + + return (relationProperty) + } + + getHasOne(className, fk) { + let relationProperty = { + decorators: [{ name: "hasOne", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], + name: className.toLocaleLowerCase(), + type: className, + } + + return (relationProperty) + } + + getBelongsTo(className, fk, fktype, sourceProperty) { + let relationProperty + relationProperty = { + decorators: [{ name: "belongsTo", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], + name: sourceProperty, + type: fktype, + } + return (relationProperty) + } + + + addRequiredImports(sourceFile, targetModel, relationName, targetClassName) { + let importsArray = this.getRequiredImports(targetModel, relationName, targetClassName); + while (importsArray.length > 0) { + let currentImport = importsArray.pop(); + this.addCurrentImport(sourceFile, currentImport); + } + } + + + getRequiredImports(targetModel, relationName, targetClassName) { + + let importsArray = [{ + name: targetClassName, + module: "./" + targetModel + ".model" + }, { + name: relationName, + module: "@loopback/repository" + }, + ]; + + return importsArray; + } + + addCurrentImport(sourceFile, currentImport) { + if (!this.doesModuleExists(sourceFile, currentImport.module)) { + sourceFile.addImportDeclaration({ + moduleSpecifier: currentImport.module + }); + } + if (!this.doesImportExistInModule(sourceFile, currentImport)) { + sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); + } + } + + + doesModuleExists(sourceFile, moduleName) { + return sourceFile.getImportDeclaration(moduleName); + } + + doesImportExistInModule(sourceFile, currentImport) { + let identicalImport; + let relevantImports = this.getNamedImportsFromModule(sourceFile, currentImport.module); + if (relevantImports.length > 0) { + identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); + } + + return (identicalImport && identicalImport.length > 0); + } + + getNamedImportsFromModule(sourceFile, moduleName) { + let allImports = sourceFile.getImportDeclarations(); + let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); + return relevantImports; + } + +} diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js new file mode 100644 index 000000000000..02c8b6f31549 --- /dev/null +++ b/packages/cli/generators/relation/controllerRelation.js @@ -0,0 +1,153 @@ +// Copyright IBM Corp. 2017,2018. All Rights Reserved. +// Node module: @loopback/cli +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +// Author: Raphael Drai at F5.com + +'use strict'; + +const _ = require('lodash'); +const ArtifactGenerator = require('../../lib/artifact-generator'); +const debug = require('../../lib/debug')('relation-generator'); +const inspect = require('util').inspect; +const path = require('path'); +const chalk = require('chalk'); +const utils = require('../../lib/utils'); +const ast = require('ts-simple-ast'); + + +const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template.ts.ejs'; + +// Exportable constants +module.exports = class ControllerRelation extends ArtifactGenerator { + // Note: arguments and options should be defined in the constructor. + constructor(args, opts) { + super(args, opts); + } + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.outDir = path.resolve( + this.artifactInfo.rootDir, + 'controllers', + ); + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + + this.option('controllerType', { + type: String, + required: false, + description: 'Type for the ' + this.artifactInfo.type, + }); + } + + + scaffold() { + // We don't want to call the base scaffold function since it copies + // all of the templates! + // we can set here additional specific this.artifactInfo.xxx parameters if needed + + return; + } + + generateRelationController(sourceModel, targetModel, foreignKey, relationName) { + + let project = new ast.Project(); + const sourceFile = this.addFileToProject(project, (this.artifactInfo.modelDir + "/" + this.options.sourceModel + ".model.ts")); + this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); + + + this.artifactInfo.foreignKey = foreignKey; + this.artifactInfo.keyType = this.options.keyType; + this.artifactInfo.repositoryVariableName = utils.camelCase( + (sourceModel + "Repo"), + ); + this.artifactInfo.srcModel = utils.camelCase( + sourceModel, + ); + this.artifactInfo.dstModel = utils.camelCase( + targetModel, + ); + this.artifactInfo.modelName = utils.toClassName( + targetModel, + ); + this.artifactInfo.className = utils.toClassName( + this.artifactInfo.srcModel + this.artifactInfo.modelName, + ); + this.artifactInfo.repositoryName = utils.toClassName( + (this.artifactInfo.srcModel + "Repository"), + ); + this.artifactInfo.relationModel = utils.toClassName( + this.artifactInfo.srcModel + "." + this.artifactInfo.modelName, + ); + + this.artifactInfo.name = utils.camelCase( + sourceModel + "-" + targetModel, + ); + + this.artifactInfo.httpPathName = "/" + sourceModel + "s"; + if (this.shouldExit()) return false; + + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + + '.controller.ts'; + if (debug.enabled) { + debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); + } + + // renames the file + let template = CONTROLLER_TEMPLATE_PATH; + + const source = this.templatePath( + template + ); + if (debug.enabled) { + debug('Using template at: ${source}'); + } + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile) + ); + + if (debug.enabled) { + debug('artifactInfo: ${inspect(this.artifactInfo)}'); + debug('Copying artifact to: ${dest}'); + } + + + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; + + } + + getKeyType(sourceFile, propertyName) { + const classObj = this.getClassObj(sourceFile); + if (classObj.getProperties().map(x => x.getName()).includes(propertyName)) { + return classObj.getProperty(propertyName).getType().getText(); + } + } + + getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } + + addFileToProject(project, fileName) { + try { + return project.addExistingSourceFile(fileName); + } catch (e) { + throw new Error("source model file: '" + fileName + "' is not found."); + } + } + +} diff --git a/packages/cli/generators/relation/repositoryRelation.js b/packages/cli/generators/relation/repositoryRelation.js new file mode 100644 index 000000000000..fceeb3cc3c36 --- /dev/null +++ b/packages/cli/generators/relation/repositoryRelation.js @@ -0,0 +1,271 @@ +const ArtifactGenerator = require('../../lib/artifact-generator'); +const debug = require('../../lib/debug')('relation-generator'); +const inspect = require('util').inspect; +const path = require('path'); +const chalk = require('chalk'); +const utils = require('../../lib/utils'); + + + +const fs = require('fs'); +const ast = require('ts-simple-ast'); + +const relationType = { + hasOne: "hasOne", + hasMany: "hasMany", + belongsTo: "belongsTo", +}; + + +module.exports = class RepositoryRelation extends ArtifactGenerator { + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + + } + + generateRelationRepository(sourceModel, targetModel, foreignKey, relationName) { + + let result; + + let projectPath = this.artifactInfo.repositoryDir; + + + + if (!this.doesRepositoryExists(projectPath, sourceModel)) { + throw Error("repository for source model doesn't exist"); + } + if (!doesRepositoryExists(projectPath, targetModel)) { + throw Error("repository for target model doesn't exist"); + } + if (relationName != relationType.hasOne && + relationName != relationType.hasMany && + relationName != relationType.belongsTo) { + throw Error("relation is invalid"); + } + + generateRelation(projectPath, sourceModel, targetModel, relationName); + } + + doesRepositoryExists(path, model) { + let tempPath = path + '/src/repositories/' + model + '.repository.ts'; + let result = fs.existsSync(tempPath); + return result; + } + + generateRelation(projectPath, sourceModel, targetModel, relationName) { + let sourceFile = initiateSourceFile(projectPath, sourceModel); + addRequiredImports(sourceFile, targetModel, relationName); + addRequiredProperties(sourceFile, sourceModel, targetModel, relationName); + editConstructor(sourceFile, sourceModel, targetModel, relationName); + sourceFile.save(); + } + + initiateSourceFile(basePath, modelName) { + + let repoPath = createRepositoryPath(basePath, modelName); + let project = new ast.Project(); + let sourceFile = project.addExistingSourceFile(repoPath); + + return sourceFile; + } + + createRepositoryPath(basePath, modelName) { + return (basePath + "/src/repositories/" + modelName + ".repository.ts"); + } + + addRequiredImports(sourceFile, targetModel, relationName) { + let importsArray = getRequiredImports(targetModel, relationName); + while (importsArray.length > 0) { + let currentImport = importsArray.pop(); + addCurrentImport(sourceFile, currentImport); + } + } + + getRequiredImports(targetModel, relationName) { + let capRelationName = capitalizeFirstLetter(relationName); + + let importsArray = getSharedImports(targetModel); + + switch (relationName) { + case (relationType.hasMany): + importsArray.push({ + name: capRelationName + "RepositoryFactory", + module: "@loopback/repository" + }); + break; + + case (relationType.hasOne): + importsArray.push({ + name: capRelationName + "RepositoryFactory", + module: "@loopback/repository" + }); + break; + + case (relationType.belongsTo): + importsArray.push({ + name: capRelationName + "Accessor", + module: "@loopback/repository" + }); + break; + + default: + result = false; + } + + return importsArray; + } + + getSharedImports(targetModel) { + let capTargetModel = capitalizeFirstLetter(targetModel); + let importsArray = [{ + name: capTargetModel, + module: "../models" + }, { + name: capTargetModel + "Repository", + module: "./index" + }, { + name: "repository", + module: "@loopback/repository" + }, { + name: "Getter", + module: "@loopback/core" + } + ]; + return importsArray; + } + + addCurrentImport(sourceFile, currentImport) { + if (!doesModuleExists(sourceFile, currentImport.module)) { + sourceFile.addImportDeclaration({ + moduleSpecifier: currentImport.module + }); + } + if (!doesImportExistInModule(sourceFile, currentImport)) { + sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); + } + } + + doesModuleExists(sourceFile, moduleName) { + return sourceFile.getImportDeclaration(moduleName); + } + + doesImportExistInModule(sourceFile, currentImport) { + let identicalImport; + let relevantImports = getNamedImportsFromModule(sourceFile, currentImport.module); + if (relevantImports.length > 0) { + identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); + } + + return (identicalImport && identicalImport.length > 0); + } + + getNamedImportsFromModule(sourceFile, moduleName) { + let allImports = sourceFile.getImportDeclarations(); + let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); + return relevantImports; + } + + capitalizeFirstLetter(string) { + string = string[0].toUpperCase() + string.substring(1); + return string; + } + + addRequiredProperties(sourceFile, sourceModel, targetModel, relationName) { + let classDeclaration = sourceFile.getClassOrThrow(capitalizeFirstLetter(sourceModel) + "Repository"); + classDeclaration.addProperty({ + scope: ast.Scope.Public, + isReadonly: true, + name: getTargetPropertyName(targetModel, relationName), + type: getTargetPropertyType(sourceModel, targetModel, relationName), + }); + orderProperties(classDeclaration); + } + + getTargetPropertyName(targetModel, relationName) { + let propertyName = targetModel[0].toLowerCase(); + propertyName += targetModel.substring(1); + + if (relationName == relationType.hasMany) { + propertyName += "s"; + } + return propertyName; + } + + getTargetPropertyType(sourceModel, targetModel, relationName) { + let propertyType = capitalizeFirstLetter(relationName); + if (relationName == relationType.belongsTo) { + propertyType += "Accessor"; + } + else if (relationName == relationType.hasOne || + relationName == relationType.hasMany) { + propertyType += "RepositoryFactory" + } + else { + throw Error("relation is invalid"); + } + propertyType += "<" + capitalizeFirstLetter(targetModel) + + ", typeof " + capitalizeFirstLetter(sourceModel) + ".prototype.id>"; + + return (propertyType); + } + + orderProperties(classDeclaration) { + classDeclaration.getProperties().forEach(function (currentProperty) { + currentProperty.setOrder(0); + }) + } + + editConstructor(sourceFile, sourceModel, targetModel, relationName) { + let capSourceModel = capitalizeFirstLetter(sourceModel); + let classDeclaration = sourceFile.getClassOrThrow(capSourceModel + "Repository"); + let classConstructor = classDeclaration.getConstructors()[0]; + addParameters(classConstructor, targetModel); + addCreator(classConstructor, targetModel, relationName); + } + + addParameters(classConstructor, targetModel) { + classConstructor.addParameter({ + decorators: [{ + name: "repository.getter", + arguments: ["\'" + capitalizeFirstLetter(targetModel) + "Repository\'"] + }], + name: targetModel + "RepositoryGetter", + type: "Getter<" + capitalizeFirstLetter(targetModel) + "Repository>," + }) + } + + addCreator(classConstructor, targetModel, relationName) { + var propertyName = getTargetPropertyName(targetModel, relationName); + var method = "this.create" + capitalizeFirstLetter(relationName); + if (relationName == relationType.belongsTo) { + method += "Accessor"; + } + else if (relationName == relationType.hasMany || + relationName == relationType.hasOne) { + method += "RepositoryFactory"; + } + else { + throw Error("relation is invalid"); + } + method += "For("; + + var parameter1 = "\'" + propertyName + "\',"; + var paramater2 = "get" + capitalizeFirstLetter(targetModel) + "Repository,"; + + let creatorStatement = "this." + propertyName + "=" + + method + parameter1 + paramater2 + ");"; + + classConstructor.insertStatements(1, creatorStatement); + } + +} diff --git a/packages/cli/generators/relation/templates/controller-relation-template.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template.ts.ejs new file mode 100644 index 000000000000..ca1590211a3f --- /dev/null +++ b/packages/cli/generators/relation/templates/controller-relation-template.ts.ejs @@ -0,0 +1,96 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/example-todo-list +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT +// Author: Raphael Drai at F5.com + +import { + Count, + CountSchema, + Filter, + repository, + Where, +} from '@loopback/repository'; +import { + del, + get, + getWhereSchemaFor, + param, + patch, + post, + requestBody, +} from '@loopback/rest'; +import {<%= modelName %>} from '../models'; +import {<%= repositoryName %>} from '../repositories'; + +export class <%= className %>Controller { + constructor( + @repository(<%= repositoryName %>) protected <%= repositoryVariableName %>: <%= repositoryName %>, + ) {} + + @post('<%= httpPathName %>/{<%= foreignKey %>}/<%= dstModel %>s', { + responses: { + '200': { + description: '<%= relationModel %> model instance', + content: {'application/json': {schema: {'x-ts-type': <%= modelName %>}}}, + }, + }, + }) + async create( + @param.path.<%= keyType %>('<%= foreignKey %>') <%= foreignKey %>: <%= keyType %>, + @requestBody() <%= dstModel %>: <%= modelName %>, + ): Promise<<%= modelName %>> { + return await this.<%= repositoryVariableName %>.<%= dstModel %>s(<%= foreignKey %>).create(<%= dstModel %>); + } + + @get('<%= httpPathName %>/{<%= foreignKey %>}/<%= dstModel %>s', { + responses: { + '200': { + description: "Array of <%= modelName %>'s belonging to <%= srcModel %>", + content: { + 'application/json': { + schema: {type: 'array', items: {'x-ts-type': <%= modelName %>}}, + }, + }, + }, + }, + }) + async find( + @param.path.<%= keyType %>('<%= foreignKey %>') <%= foreignKey %>: <%= keyType %>, + @param.query.object('filter') filter?: Filter, + ): Promise<<%= modelName %>[]> { + return await this.<%= repositoryVariableName %>.<%= dstModel %>s(<%= foreignKey %>).find(filter); + } + + @patch('<%= httpPathName %>/{<%= foreignKey %>}/<%= dstModel %>s', { + responses: { + '200': { + description: '<%= relationModel %> PATCH success count', + content: {'application/json': {schema: CountSchema}}, + }, + }, + }) + async patch( + @param.path.<%= keyType %>('<%= foreignKey %>') <%= foreignKey %>: <%= keyType %>, + @requestBody() <%= dstModel %>: Partial<<%= modelName %>>, + @param.query.object('where', getWhereSchemaFor(<%= modelName %>)) where?: Where, + ): Promise { + return await this.<%= repositoryVariableName %>.<%= dstModel %>s(<%= foreignKey %>).patch(<%= dstModel %>, where); + } + + @del('<%= httpPathName %>/{<%= foreignKey %>}/<%= dstModel %>s', { + responses: { + '200': { + description: '<%= relationModel %> DELETE success count', + content: {'application/json': {schema: CountSchema}}, + }, + }, + }) + async delete( + @param.path.<%= keyType %>('<%= foreignKey %>') <%= foreignKey %>: <%= keyType %>, + @param.query.object('where', getWhereSchemaFor(<%= modelName %>)) where?: Where, + ): Promise { + return await this.<%= repositoryVariableName %>.<%= dstModel %>s(<%= foreignKey %>).delete(where); + } + +} From d6d5befcf689c4756d549e3ef2b4222c192a73a0 Mon Sep 17 00:00:00 2001 From: "s.itzhak" Date: Wed, 23 Jan 2019 14:50:39 +0200 Subject: [PATCH 04/89] feat(cli): run npm lint on lb4 relation new files solution for issue #1359:loopback-next support lb4 relation command after npm run lint:fix --- .../cli/generators/relation/ modelRelation.js | 580 +++++++++++------- .../generators/relation/controllerRelation.js | 244 ++++---- .../generators/relation/repositoryRelation.js | 488 ++++++++------- 3 files changed, 721 insertions(+), 591 deletions(-) diff --git a/packages/cli/generators/relation/ modelRelation.js b/packages/cli/generators/relation/ modelRelation.js index f18a07f149d1..b313efdbd1a0 100644 --- a/packages/cli/generators/relation/ modelRelation.js +++ b/packages/cli/generators/relation/ modelRelation.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; const ArtifactGenerator = require('../../lib/artifact-generator'); const debug = require('../../lib/debug')('relation-generator'); const inspect = require('util').inspect; @@ -8,255 +8,367 @@ const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); module.exports = class ModelRelation extends ArtifactGenerator { - - _setupGenerator() { - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - this.artifactInfo.modelDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + } + + generateRelationModel(sourceModel, targetModel, foreignKey, relationName) { + let modelPath = this.artifactInfo.modelDir; + if (relationName == 'hasMany' || relationName == 'hasOne') { + this.generateModel( + sourceModel, + targetModel, + relationName, + modelPath, + foreignKey, + undefined, + ); + this.generateModel( + targetModel, + sourceModel, + 'belongsTo', + modelPath, + 'id', + foreignKey, + ); + } else { + this.generateModel( + sourceModel, + targetModel, + relationName, + modelPath, + 'id', + foreignKey, + ); } - - generateRelationModel(sourceModel, targetModel, foreignKey, relationName) { - let modelPath = this.artifactInfo.modelDir; - if ((relationName == "hasMany") || (relationName == "hasOne")) { - this.generateModel(sourceModel, targetModel, relationName, modelPath, foreignKey, undefined); - this.generateModel(targetModel, sourceModel, "belongsTo", modelPath, "id", foreignKey); - } - else { - this.generateModel(sourceModel, targetModel, relationName, modelPath, "id", foreignKey); - } + } + + generateModel( + sourceModel, + targetModel, + relationName, + path, + foreignKey, + sourceForeignKey, + ) { + let project = new ast.Project(); + + const sourceFile = this.addFileToProject(project, path, sourceModel); + if (!this.isClassExist(sourceFile)) { + return; } + const sourceClass = this.getClassObj(sourceFile); - generateModel(sourceModel, targetModel, relationName, path, foreignKey, sourceForeignKey) { - let project = new ast.Project(); - - const sourceFile = this.addFileToProject(project, path, sourceModel); - if (!this.isClassExist(sourceFile)) { - return; - } - const sourceClass = this.getClassObj(sourceFile); - - const targetFile = this.addFileToProject(project, path, targetModel); - if (!this.isClassExist(targetFile)) { - return; + const targetFile = this.addFileToProject(project, path, targetModel); + if (!this.isClassExist(targetFile)) { + return; + } + const targetClass = this.getClassObj(targetFile); + let modelProperty; + const lowerCaseTargetClassName = this.getClassNameLowerCase(targetFile); + const newPropertyName = this.getForeignKey(sourceClass, foreignKey); + + switch (relationName) { + case 'hasMany': + if (this.isPropertyExist(sourceClass, lowerCaseTargetClassName + 's')) { + // TODO add error to CLI UI + throw new Error( + ' property ' + lowerCaseTargetClassName + 's exist in the model', + ); } - const targetClass = this.getClassObj(targetFile); - let modelProperty; - const lowerCaseTargetClassName = this.getClassNameLowerCase(targetFile) - const newPropertyName = this.getForeignKey(sourceClass, foreignKey) - - - switch (relationName) { - case "hasMany": - if (this.isPropertyExist((sourceClass), (lowerCaseTargetClassName + 's'))) { - // TODO add error to CLI UI - throw new Error(' property ' + lowerCaseTargetClassName + 's exist in the model') - } - if (this.isPropertyExist((targetClass), newPropertyName)) { - // TODO add error to CLI UI - throw new Error('wrong property ' + newPropertyName + ' in the target model') - } - else { - modelProperty = this.getHasMany(targetClass.getName(), newPropertyName); - } - break; - case "hasOne": - if (this.isPropertyExist((sourceClass), (lowerCaseTargetClassName))) { - // TODO add error to CLI UI - throw new Error('property ' + lowerCaseTargetClassName + ' exist in the model') - } - if (this.isPropertyExist((targetClass), newPropertyName)) { - // TODO add error to CLI UI - throw new Error('wrong property ' + newPropertyName + ' in the target model') - } - else { - modelProperty = this.getHasOne(targetClass.getName(), newPropertyName); - } - break; - case "belongsTo": - if (this.isPropertyExist((sourceClass), (this.getForeignKey(targetClass, sourceForeignKey)))) { - // TODO add error to CLI UI - throw new Error('property ' + lowerCaseTargetClassName + 'Id exist in the model') - } - let belongsToKey = this.getForeignKeyBelongsTo(sourceClass, foreignKey); - if (!(this.isPropertyExist((targetClass), belongsToKey))) { - // TODO add error to CLI UI - throw new Error('wrong property ' + belongsToKey + ' in the target model') - } - else { - modelProperty = this.getBelongsTo(targetClass.getName(), belongsToKey, this.getKeyType(targetClass, belongsToKey), this.getForeignKey(targetClass, sourceForeignKey)); - } - break; + if (this.isPropertyExist(targetClass, newPropertyName)) { + // TODO add error to CLI UI + throw new Error( + 'wrong property ' + newPropertyName + ' in the target model', + ); + } else { + modelProperty = this.getHasMany( + targetClass.getName(), + newPropertyName, + ); } - sourceClass.insertProperty(this.getPropertiesCount(sourceClass), modelProperty); - sourceClass.insertText(this.getPropertyStartPos(sourceClass), "\n") - this.addRequiredImports(sourceFile, targetModel, relationName, targetClass.getName()); - sourceClass.formatText() - sourceFile.save(); - } - addFileToProject(project, path, modelName) { - const fileName = path + "/" + modelName + '.model.ts'; - return project.addExistingSourceFile(fileName); - } - - getClassesCount(fileName) { - return fileName.getClasses().length; - } - - - - getClassObj(fileName) { - const className = fileName.getClasses()[0].getNameOrThrow(); - return fileName.getClassOrThrow(className); - } - - - isClassExist(fileName) { - return (this.getClassesCount(fileName) == 1); - } - - getPropertiesCount(classObj) { - return classObj.getProperties().length; - } - - getPropertyStartPos(classObj) { - return classObj.getChildSyntaxList().getChildAtIndex(this.getPropertiesCount(classObj) - 1).getPos() - } - - getClassProperties(classObj) { - return classObj.getProperties() - } - - isPropertyExist(classObj, propertyName) { - return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) - } - - getClassNameLowerCase(fileName) { - let name = fileName.getClasses()[0].getNameOrThrow() - return name.charAt(0).toLowerCase() + name.slice(1) - } - - getKey(classObj) { - for (let i = 0; i < this.getPropertiesCount(classObj); i++) { - if (classObj.getProperties()[i].getDecorators()[0].getName() == 'property') { - if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperties().map(x => x.getName()).includes('id')) { - if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperty('id').getInitializer().getText() == 'true') { - return (classObj.getProperties()[i].getName()) - } - } - } + break; + case 'hasOne': + if (this.isPropertyExist(sourceClass, lowerCaseTargetClassName)) { + // TODO add error to CLI UI + throw new Error( + 'property ' + lowerCaseTargetClassName + ' exist in the model', + ); } - throw new Error(' primary Key is missing ') - } - - getForeignKey(classObj, foreignKey) { - let name = classObj.getName() - return (name.charAt(0).toLowerCase() + name.slice(1) + foreignKey.charAt(0).toUpperCase() + foreignKey.slice(1)) - - } - - getForeignKeyBelongsTo(classObj, foreignKey) { - if (foreignKey === undefined) { - return (this.getKey(classObj)) + if (this.isPropertyExist(targetClass, newPropertyName)) { + // TODO add error to CLI UI + throw new Error( + 'wrong property ' + newPropertyName + ' in the target model', + ); + } else { + modelProperty = this.getHasOne( + targetClass.getName(), + newPropertyName, + ); } - return (foreignKey) - } - - getKeyType(classObj, propertyName) { - return classObj.getProperty(propertyName).getType().getText() - } - - - getHasMany(className, fk) { - let relationProperty = { - decorators: [{ name: "hasMany", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], - name: className.toLocaleLowerCase() + "s", - type: className + "[]", + break; + case 'belongsTo': + if ( + this.isPropertyExist( + sourceClass, + this.getForeignKey(targetClass, sourceForeignKey), + ) + ) { + // TODO add error to CLI UI + throw new Error( + 'property ' + lowerCaseTargetClassName + 'Id exist in the model', + ); } - - return (relationProperty) - } - - getHasOne(className, fk) { - let relationProperty = { - decorators: [{ name: "hasOne", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], - name: className.toLocaleLowerCase(), - type: className, + let belongsToKey = this.getForeignKeyBelongsTo(sourceClass, foreignKey); + if (!this.isPropertyExist(targetClass, belongsToKey)) { + // TODO add error to CLI UI + throw new Error( + 'wrong property ' + belongsToKey + ' in the target model', + ); + } else { + modelProperty = this.getBelongsTo( + targetClass.getName(), + belongsToKey, + this.getKeyType(targetClass, belongsToKey), + this.getForeignKey(targetClass, sourceForeignKey), + ); } - - return (relationProperty) + break; } - - getBelongsTo(className, fk, fktype, sourceProperty) { - let relationProperty - relationProperty = { - decorators: [{ name: "belongsTo", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], - name: sourceProperty, - type: fktype, + sourceClass.insertProperty( + this.getPropertiesCount(sourceClass), + modelProperty, + ); + sourceClass.insertText(this.getPropertyStartPos(sourceClass), '\n'); + this.addRequiredImports( + sourceFile, + targetModel, + relationName, + targetClass.getName(), + ); + sourceClass.formatText(); + sourceFile.save(); + } + addFileToProject(project, path, modelName) { + const fileName = path + '/' + modelName + '.model.ts'; + return project.addExistingSourceFile(fileName); + } + + getClassesCount(fileName) { + return fileName.getClasses().length; + } + + getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } + + isClassExist(fileName) { + return this.getClassesCount(fileName) == 1; + } + + getPropertiesCount(classObj) { + return classObj.getProperties().length; + } + + getPropertyStartPos(classObj) { + return classObj + .getChildSyntaxList() + .getChildAtIndex(this.getPropertiesCount(classObj) - 1) + .getPos(); + } + + getClassProperties(classObj) { + return classObj.getProperties(); + } + + isPropertyExist(classObj, propertyName) { + return this.getClassProperties(classObj) + .map(x => x.getName()) + .includes(propertyName); + } + + getClassNameLowerCase(fileName) { + let name = fileName.getClasses()[0].getNameOrThrow(); + return name.charAt(0).toLowerCase() + name.slice(1); + } + + getKey(classObj) { + for (let i = 0; i < this.getPropertiesCount(classObj); i++) { + if ( + classObj + .getProperties() + [i].getDecorators()[0] + .getName() == 'property' + ) { + if ( + classObj + .getProperties() + [i].getDecorators()[0] + .getArguments()[0] + .getProperties() + .map(x => x.getName()) + .includes('id') + ) { + if ( + classObj + .getProperties() + [i].getDecorators()[0] + .getArguments()[0] + .getProperty('id') + .getInitializer() + .getText() == 'true' + ) { + return classObj.getProperties()[i].getName(); + } } - return (relationProperty) + } } - - - addRequiredImports(sourceFile, targetModel, relationName, targetClassName) { - let importsArray = this.getRequiredImports(targetModel, relationName, targetClassName); - while (importsArray.length > 0) { - let currentImport = importsArray.pop(); - this.addCurrentImport(sourceFile, currentImport); - } + throw new Error(' primary Key is missing '); + } + + getForeignKey(classObj, foreignKey) { + let name = classObj.getName(); + return ( + name.charAt(0).toLowerCase() + + name.slice(1) + + foreignKey.charAt(0).toUpperCase() + + foreignKey.slice(1) + ); + } + + getForeignKeyBelongsTo(classObj, foreignKey) { + if (foreignKey === undefined) { + return this.getKey(classObj); } - - - getRequiredImports(targetModel, relationName, targetClassName) { - - let importsArray = [{ - name: targetClassName, - module: "./" + targetModel + ".model" - }, { - name: relationName, - module: "@loopback/repository" + return foreignKey; + } + + getKeyType(classObj, propertyName) { + return classObj + .getProperty(propertyName) + .getType() + .getText(); + } + + getHasMany(className, fk) { + let relationProperty = { + decorators: [ + { + name: 'hasMany', + arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"], }, - ]; - - return importsArray; - } - - addCurrentImport(sourceFile, currentImport) { - if (!this.doesModuleExists(sourceFile, currentImport.module)) { - sourceFile.addImportDeclaration({ - moduleSpecifier: currentImport.module - }); - } - if (!this.doesImportExistInModule(sourceFile, currentImport)) { - sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); - } + ], + name: className.toLocaleLowerCase() + 's', + type: className + '[]', + }; + + return relationProperty; + } + + getHasOne(className, fk) { + let relationProperty = { + decorators: [ + { + name: 'hasOne', + arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"], + }, + ], + name: className.toLocaleLowerCase(), + type: className, + }; + + return relationProperty; + } + + getBelongsTo(className, fk, fktype, sourceProperty) { + let relationProperty; + relationProperty = { + decorators: [ + { + name: 'belongsTo', + arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"], + }, + ], + name: sourceProperty, + type: fktype, + }; + return relationProperty; + } + + addRequiredImports(sourceFile, targetModel, relationName, targetClassName) { + let importsArray = this.getRequiredImports( + targetModel, + relationName, + targetClassName, + ); + while (importsArray.length > 0) { + let currentImport = importsArray.pop(); + this.addCurrentImport(sourceFile, currentImport); } - - - doesModuleExists(sourceFile, moduleName) { - return sourceFile.getImportDeclaration(moduleName); + } + + getRequiredImports(targetModel, relationName, targetClassName) { + let importsArray = [ + { + name: targetClassName, + module: './' + targetModel + '.model', + }, + { + name: relationName, + module: '@loopback/repository', + }, + ]; + + return importsArray; + } + + addCurrentImport(sourceFile, currentImport) { + if (!this.doesModuleExists(sourceFile, currentImport.module)) { + sourceFile.addImportDeclaration({ + moduleSpecifier: currentImport.module, + }); } - - doesImportExistInModule(sourceFile, currentImport) { - let identicalImport; - let relevantImports = this.getNamedImportsFromModule(sourceFile, currentImport.module); - if (relevantImports.length > 0) { - identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); - } - - return (identicalImport && identicalImport.length > 0); + if (!this.doesImportExistInModule(sourceFile, currentImport)) { + sourceFile + .getImportDeclarationOrThrow(currentImport.module) + .addNamedImport(currentImport.name); } - - getNamedImportsFromModule(sourceFile, moduleName) { - let allImports = sourceFile.getImportDeclarations(); - let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); - return relevantImports; + } + + doesModuleExists(sourceFile, moduleName) { + return sourceFile.getImportDeclaration(moduleName); + } + + doesImportExistInModule(sourceFile, currentImport) { + let identicalImport; + let relevantImports = this.getNamedImportsFromModule( + sourceFile, + currentImport.module, + ); + if (relevantImports.length > 0) { + identicalImport = relevantImports[0] + .getNamedImports() + .filter(imp => imp.getName() == currentImport.name); } -} + return identicalImport && identicalImport.length > 0; + } + + getNamedImportsFromModule(sourceFile, moduleName) { + let allImports = sourceFile.getImportDeclarations(); + let relevantImports = allImports.filter( + imp => imp.getModuleSpecifierValue() == moduleName, + ); + return relevantImports; + } +}; diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 02c8b6f31549..8c596f703816 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -15,139 +15,137 @@ const chalk = require('chalk'); const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); - const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template.ts.ejs'; // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { - // Note: arguments and options should be defined in the constructor. - constructor(args, opts) { - super(args, opts); - } - - _setupGenerator() { - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - this.artifactInfo.outDir = path.resolve( - this.artifactInfo.rootDir, - 'controllers', - ); - this.artifactInfo.modelDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - this.artifactInfo.repositoryDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories', - ); - - this.option('controllerType', { - type: String, - required: false, - description: 'Type for the ' + this.artifactInfo.type, - }); + // Note: arguments and options should be defined in the constructor. + constructor(args, opts) { + super(args, opts); + } + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.outDir = path.resolve( + this.artifactInfo.rootDir, + 'controllers', + ); + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + + this.option('controllerType', { + type: String, + required: false, + description: 'Type for the ' + this.artifactInfo.type, + }); + } + + scaffold() { + // We don't want to call the base scaffold function since it copies + // all of the templates! + // we can set here additional specific this.artifactInfo.xxx parameters if needed + + return; + } + + generateRelationController( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { + let project = new ast.Project(); + const sourceFile = this.addFileToProject( + project, + this.artifactInfo.modelDir + '/' + this.options.sourceModel + '.model.ts', + ); + this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); + + this.artifactInfo.foreignKey = foreignKey; + this.artifactInfo.keyType = this.options.keyType; + this.artifactInfo.repositoryVariableName = utils.camelCase( + sourceModel + 'Repo', + ); + this.artifactInfo.srcModel = utils.camelCase(sourceModel); + this.artifactInfo.dstModel = utils.camelCase(targetModel); + this.artifactInfo.modelName = utils.toClassName(targetModel); + this.artifactInfo.className = utils.toClassName( + this.artifactInfo.srcModel + this.artifactInfo.modelName, + ); + this.artifactInfo.repositoryName = utils.toClassName( + this.artifactInfo.srcModel + 'Repository', + ); + this.artifactInfo.relationModel = utils.toClassName( + this.artifactInfo.srcModel + '.' + this.artifactInfo.modelName, + ); + + this.artifactInfo.name = utils.camelCase(sourceModel + '-' + targetModel); + + this.artifactInfo.httpPathName = '/' + sourceModel + 's'; + if (this.shouldExit()) return false; + + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + if (debug.enabled) { + debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); } + // renames the file + let template = CONTROLLER_TEMPLATE_PATH; - scaffold() { - // We don't want to call the base scaffold function since it copies - // all of the templates! - // we can set here additional specific this.artifactInfo.xxx parameters if needed - - return; + const source = this.templatePath(template); + if (debug.enabled) { + debug('Using template at: ${source}'); } + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), + ); - generateRelationController(sourceModel, targetModel, foreignKey, relationName) { - - let project = new ast.Project(); - const sourceFile = this.addFileToProject(project, (this.artifactInfo.modelDir + "/" + this.options.sourceModel + ".model.ts")); - this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); - - - this.artifactInfo.foreignKey = foreignKey; - this.artifactInfo.keyType = this.options.keyType; - this.artifactInfo.repositoryVariableName = utils.camelCase( - (sourceModel + "Repo"), - ); - this.artifactInfo.srcModel = utils.camelCase( - sourceModel, - ); - this.artifactInfo.dstModel = utils.camelCase( - targetModel, - ); - this.artifactInfo.modelName = utils.toClassName( - targetModel, - ); - this.artifactInfo.className = utils.toClassName( - this.artifactInfo.srcModel + this.artifactInfo.modelName, - ); - this.artifactInfo.repositoryName = utils.toClassName( - (this.artifactInfo.srcModel + "Repository"), - ); - this.artifactInfo.relationModel = utils.toClassName( - this.artifactInfo.srcModel + "." + this.artifactInfo.modelName, - ); - - this.artifactInfo.name = utils.camelCase( - sourceModel + "-" + targetModel, - ); - - this.artifactInfo.httpPathName = "/" + sourceModel + "s"; - if (this.shouldExit()) return false; - - this.artifactInfo.outFile = - utils.kebabCase(this.artifactInfo.name) + - '.controller.ts'; - if (debug.enabled) { - debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); - } - - // renames the file - let template = CONTROLLER_TEMPLATE_PATH; - - const source = this.templatePath( - template - ); - if (debug.enabled) { - debug('Using template at: ${source}'); - } - const dest = this.destinationPath( - path.join(this.artifactInfo.outDir, this.artifactInfo.outFile) - ); - - if (debug.enabled) { - debug('artifactInfo: ${inspect(this.artifactInfo)}'); - debug('Copying artifact to: ${dest}'); - } - - - this.copyTemplatedFiles(source, dest, this.artifactInfo); - return; - + if (debug.enabled) { + debug('artifactInfo: ${inspect(this.artifactInfo)}'); + debug('Copying artifact to: ${dest}'); } - getKeyType(sourceFile, propertyName) { - const classObj = this.getClassObj(sourceFile); - if (classObj.getProperties().map(x => x.getName()).includes(propertyName)) { - return classObj.getProperty(propertyName).getType().getText(); - } + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; + } + + getKeyType(sourceFile, propertyName) { + const classObj = this.getClassObj(sourceFile); + if ( + classObj + .getProperties() + .map(x => x.getName()) + .includes(propertyName) + ) { + return classObj + .getProperty(propertyName) + .getType() + .getText(); } - - getClassObj(fileName) { - const className = fileName.getClasses()[0].getNameOrThrow(); - return fileName.getClassOrThrow(className); - } - - addFileToProject(project, fileName) { - try { - return project.addExistingSourceFile(fileName); - } catch (e) { - throw new Error("source model file: '" + fileName + "' is not found."); - } + } + + getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } + + addFileToProject(project, fileName) { + try { + return project.addExistingSourceFile(fileName); + } catch (e) { + throw new Error("source model file: '" + fileName + "' is not found."); } - -} + } +}; diff --git a/packages/cli/generators/relation/repositoryRelation.js b/packages/cli/generators/relation/repositoryRelation.js index fceeb3cc3c36..25b3a793a4fb 100644 --- a/packages/cli/generators/relation/repositoryRelation.js +++ b/packages/cli/generators/relation/repositoryRelation.js @@ -5,267 +5,287 @@ const path = require('path'); const chalk = require('chalk'); const utils = require('../../lib/utils'); - - const fs = require('fs'); const ast = require('ts-simple-ast'); const relationType = { - hasOne: "hasOne", - hasMany: "hasMany", - belongsTo: "belongsTo", + hasOne: 'hasOne', + hasMany: 'hasMany', + belongsTo: 'belongsTo', }; - module.exports = class RepositoryRelation extends ArtifactGenerator { - - _setupGenerator() { - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - this.artifactInfo.repositoryDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories', - ); - - } - - generateRelationRepository(sourceModel, targetModel, foreignKey, relationName) { - - let result; - - let projectPath = this.artifactInfo.repositoryDir; - - - - if (!this.doesRepositoryExists(projectPath, sourceModel)) { - throw Error("repository for source model doesn't exist"); - } - if (!doesRepositoryExists(projectPath, targetModel)) { - throw Error("repository for target model doesn't exist"); - } - if (relationName != relationType.hasOne && - relationName != relationType.hasMany && - relationName != relationType.belongsTo) { - throw Error("relation is invalid"); - } - - generateRelation(projectPath, sourceModel, targetModel, relationName); - } - - doesRepositoryExists(path, model) { - let tempPath = path + '/src/repositories/' + model + '.repository.ts'; - let result = fs.existsSync(tempPath); - return result; - } - - generateRelation(projectPath, sourceModel, targetModel, relationName) { - let sourceFile = initiateSourceFile(projectPath, sourceModel); - addRequiredImports(sourceFile, targetModel, relationName); - addRequiredProperties(sourceFile, sourceModel, targetModel, relationName); - editConstructor(sourceFile, sourceModel, targetModel, relationName); - sourceFile.save(); - } - - initiateSourceFile(basePath, modelName) { - - let repoPath = createRepositoryPath(basePath, modelName); - let project = new ast.Project(); - let sourceFile = project.addExistingSourceFile(repoPath); - - return sourceFile; + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + } + + generateRelationRepository( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { + let result; + + let projectPath = this.artifactInfo.repositoryDir; + + if (!this.doesRepositoryExists(projectPath, sourceModel)) { + throw Error("repository for source model doesn't exist"); } - - createRepositoryPath(basePath, modelName) { - return (basePath + "/src/repositories/" + modelName + ".repository.ts"); + if (!doesRepositoryExists(projectPath, targetModel)) { + throw Error("repository for target model doesn't exist"); } - - addRequiredImports(sourceFile, targetModel, relationName) { - let importsArray = getRequiredImports(targetModel, relationName); - while (importsArray.length > 0) { - let currentImport = importsArray.pop(); - addCurrentImport(sourceFile, currentImport); - } + if ( + relationName != relationType.hasOne && + relationName != relationType.hasMany && + relationName != relationType.belongsTo + ) { + throw Error('relation is invalid'); } - getRequiredImports(targetModel, relationName) { - let capRelationName = capitalizeFirstLetter(relationName); - - let importsArray = getSharedImports(targetModel); - - switch (relationName) { - case (relationType.hasMany): - importsArray.push({ - name: capRelationName + "RepositoryFactory", - module: "@loopback/repository" - }); - break; - - case (relationType.hasOne): - importsArray.push({ - name: capRelationName + "RepositoryFactory", - module: "@loopback/repository" - }); - break; - - case (relationType.belongsTo): - importsArray.push({ - name: capRelationName + "Accessor", - module: "@loopback/repository" - }); - break; - - default: - result = false; - } - - return importsArray; + generateRelation(projectPath, sourceModel, targetModel, relationName); + } + + doesRepositoryExists(path, model) { + let tempPath = path + '/src/repositories/' + model + '.repository.ts'; + let result = fs.existsSync(tempPath); + return result; + } + + generateRelation(projectPath, sourceModel, targetModel, relationName) { + let sourceFile = initiateSourceFile(projectPath, sourceModel); + addRequiredImports(sourceFile, targetModel, relationName); + addRequiredProperties(sourceFile, sourceModel, targetModel, relationName); + editConstructor(sourceFile, sourceModel, targetModel, relationName); + sourceFile.save(); + } + + initiateSourceFile(basePath, modelName) { + let repoPath = createRepositoryPath(basePath, modelName); + let project = new ast.Project(); + let sourceFile = project.addExistingSourceFile(repoPath); + + return sourceFile; + } + + createRepositoryPath(basePath, modelName) { + return basePath + '/src/repositories/' + modelName + '.repository.ts'; + } + + addRequiredImports(sourceFile, targetModel, relationName) { + let importsArray = getRequiredImports(targetModel, relationName); + while (importsArray.length > 0) { + let currentImport = importsArray.pop(); + addCurrentImport(sourceFile, currentImport); } + } - getSharedImports(targetModel) { - let capTargetModel = capitalizeFirstLetter(targetModel); - let importsArray = [{ - name: capTargetModel, - module: "../models" - }, { - name: capTargetModel + "Repository", - module: "./index" - }, { - name: "repository", - module: "@loopback/repository" - }, { - name: "Getter", - module: "@loopback/core" - } - ]; - return importsArray; - } + getRequiredImports(targetModel, relationName) { + let capRelationName = capitalizeFirstLetter(relationName); - addCurrentImport(sourceFile, currentImport) { - if (!doesModuleExists(sourceFile, currentImport.module)) { - sourceFile.addImportDeclaration({ - moduleSpecifier: currentImport.module - }); - } - if (!doesImportExistInModule(sourceFile, currentImport)) { - sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); - } - } + let importsArray = getSharedImports(targetModel); - doesModuleExists(sourceFile, moduleName) { - return sourceFile.getImportDeclaration(moduleName); - } + switch (relationName) { + case relationType.hasMany: + importsArray.push({ + name: capRelationName + 'RepositoryFactory', + module: '@loopback/repository', + }); + break; - doesImportExistInModule(sourceFile, currentImport) { - let identicalImport; - let relevantImports = getNamedImportsFromModule(sourceFile, currentImport.module); - if (relevantImports.length > 0) { - identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); - } + case relationType.hasOne: + importsArray.push({ + name: capRelationName + 'RepositoryFactory', + module: '@loopback/repository', + }); + break; - return (identicalImport && identicalImport.length > 0); - } + case relationType.belongsTo: + importsArray.push({ + name: capRelationName + 'Accessor', + module: '@loopback/repository', + }); + break; - getNamedImportsFromModule(sourceFile, moduleName) { - let allImports = sourceFile.getImportDeclarations(); - let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); - return relevantImports; + default: + result = false; } - capitalizeFirstLetter(string) { - string = string[0].toUpperCase() + string.substring(1); - return string; + return importsArray; + } + + getSharedImports(targetModel) { + let capTargetModel = capitalizeFirstLetter(targetModel); + let importsArray = [ + { + name: capTargetModel, + module: '../models', + }, + { + name: capTargetModel + 'Repository', + module: './index', + }, + { + name: 'repository', + module: '@loopback/repository', + }, + { + name: 'Getter', + module: '@loopback/core', + }, + ]; + return importsArray; + } + + addCurrentImport(sourceFile, currentImport) { + if (!doesModuleExists(sourceFile, currentImport.module)) { + sourceFile.addImportDeclaration({ + moduleSpecifier: currentImport.module, + }); } - - addRequiredProperties(sourceFile, sourceModel, targetModel, relationName) { - let classDeclaration = sourceFile.getClassOrThrow(capitalizeFirstLetter(sourceModel) + "Repository"); - classDeclaration.addProperty({ - scope: ast.Scope.Public, - isReadonly: true, - name: getTargetPropertyName(targetModel, relationName), - type: getTargetPropertyType(sourceModel, targetModel, relationName), - }); - orderProperties(classDeclaration); + if (!doesImportExistInModule(sourceFile, currentImport)) { + sourceFile + .getImportDeclarationOrThrow(currentImport.module) + .addNamedImport(currentImport.name); } - - getTargetPropertyName(targetModel, relationName) { - let propertyName = targetModel[0].toLowerCase(); - propertyName += targetModel.substring(1); - - if (relationName == relationType.hasMany) { - propertyName += "s"; - } - return propertyName; + } + + doesModuleExists(sourceFile, moduleName) { + return sourceFile.getImportDeclaration(moduleName); + } + + doesImportExistInModule(sourceFile, currentImport) { + let identicalImport; + let relevantImports = getNamedImportsFromModule( + sourceFile, + currentImport.module, + ); + if (relevantImports.length > 0) { + identicalImport = relevantImports[0] + .getNamedImports() + .filter(imp => imp.getName() == currentImport.name); } - getTargetPropertyType(sourceModel, targetModel, relationName) { - let propertyType = capitalizeFirstLetter(relationName); - if (relationName == relationType.belongsTo) { - propertyType += "Accessor"; - } - else if (relationName == relationType.hasOne || - relationName == relationType.hasMany) { - propertyType += "RepositoryFactory" - } - else { - throw Error("relation is invalid"); - } - propertyType += "<" + capitalizeFirstLetter(targetModel) + - ", typeof " + capitalizeFirstLetter(sourceModel) + ".prototype.id>"; - - return (propertyType); + return identicalImport && identicalImport.length > 0; + } + + getNamedImportsFromModule(sourceFile, moduleName) { + let allImports = sourceFile.getImportDeclarations(); + let relevantImports = allImports.filter( + imp => imp.getModuleSpecifierValue() == moduleName, + ); + return relevantImports; + } + + capitalizeFirstLetter(string) { + string = string[0].toUpperCase() + string.substring(1); + return string; + } + + addRequiredProperties(sourceFile, sourceModel, targetModel, relationName) { + let classDeclaration = sourceFile.getClassOrThrow( + capitalizeFirstLetter(sourceModel) + 'Repository', + ); + classDeclaration.addProperty({ + scope: ast.Scope.Public, + isReadonly: true, + name: getTargetPropertyName(targetModel, relationName), + type: getTargetPropertyType(sourceModel, targetModel, relationName), + }); + orderProperties(classDeclaration); + } + + getTargetPropertyName(targetModel, relationName) { + let propertyName = targetModel[0].toLowerCase(); + propertyName += targetModel.substring(1); + + if (relationName == relationType.hasMany) { + propertyName += 's'; } - - orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function (currentProperty) { - currentProperty.setOrder(0); - }) + return propertyName; + } + + getTargetPropertyType(sourceModel, targetModel, relationName) { + let propertyType = capitalizeFirstLetter(relationName); + if (relationName == relationType.belongsTo) { + propertyType += 'Accessor'; + } else if ( + relationName == relationType.hasOne || + relationName == relationType.hasMany + ) { + propertyType += 'RepositoryFactory'; + } else { + throw Error('relation is invalid'); } - - editConstructor(sourceFile, sourceModel, targetModel, relationName) { - let capSourceModel = capitalizeFirstLetter(sourceModel); - let classDeclaration = sourceFile.getClassOrThrow(capSourceModel + "Repository"); - let classConstructor = classDeclaration.getConstructors()[0]; - addParameters(classConstructor, targetModel); - addCreator(classConstructor, targetModel, relationName); + propertyType += + '<' + + capitalizeFirstLetter(targetModel) + + ', typeof ' + + capitalizeFirstLetter(sourceModel) + + '.prototype.id>'; + + return propertyType; + } + + orderProperties(classDeclaration) { + classDeclaration.getProperties().forEach(function(currentProperty) { + currentProperty.setOrder(0); + }); + } + + editConstructor(sourceFile, sourceModel, targetModel, relationName) { + let capSourceModel = capitalizeFirstLetter(sourceModel); + let classDeclaration = sourceFile.getClassOrThrow( + capSourceModel + 'Repository', + ); + let classConstructor = classDeclaration.getConstructors()[0]; + addParameters(classConstructor, targetModel); + addCreator(classConstructor, targetModel, relationName); + } + + addParameters(classConstructor, targetModel) { + classConstructor.addParameter({ + decorators: [ + { + name: 'repository.getter', + arguments: ["'" + capitalizeFirstLetter(targetModel) + "Repository'"], + }, + ], + name: targetModel + 'RepositoryGetter', + type: 'Getter<' + capitalizeFirstLetter(targetModel) + 'Repository>,', + }); + } + + addCreator(classConstructor, targetModel, relationName) { + var propertyName = getTargetPropertyName(targetModel, relationName); + var method = 'this.create' + capitalizeFirstLetter(relationName); + if (relationName == relationType.belongsTo) { + method += 'Accessor'; + } else if ( + relationName == relationType.hasMany || + relationName == relationType.hasOne + ) { + method += 'RepositoryFactory'; + } else { + throw Error('relation is invalid'); } + method += 'For('; - addParameters(classConstructor, targetModel) { - classConstructor.addParameter({ - decorators: [{ - name: "repository.getter", - arguments: ["\'" + capitalizeFirstLetter(targetModel) + "Repository\'"] - }], - name: targetModel + "RepositoryGetter", - type: "Getter<" + capitalizeFirstLetter(targetModel) + "Repository>," - }) - } + var parameter1 = "'" + propertyName + "',"; + var paramater2 = 'get' + capitalizeFirstLetter(targetModel) + 'Repository,'; - addCreator(classConstructor, targetModel, relationName) { - var propertyName = getTargetPropertyName(targetModel, relationName); - var method = "this.create" + capitalizeFirstLetter(relationName); - if (relationName == relationType.belongsTo) { - method += "Accessor"; - } - else if (relationName == relationType.hasMany || - relationName == relationType.hasOne) { - method += "RepositoryFactory"; - } - else { - throw Error("relation is invalid"); - } - method += "For("; - - var parameter1 = "\'" + propertyName + "\',"; - var paramater2 = "get" + capitalizeFirstLetter(targetModel) + "Repository,"; - - let creatorStatement = "this." + propertyName + "=" + - method + parameter1 + paramater2 + ");"; - - classConstructor.insertStatements(1, creatorStatement); - } + let creatorStatement = + 'this.' + propertyName + '=' + method + parameter1 + paramater2 + ');'; -} + classConstructor.insertStatements(1, creatorStatement); + } +}; From e807c9d64dcb214df62c40153e2bd23bb167d734 Mon Sep 17 00:00:00 2001 From: Raphael Drai Date: Mon, 28 Jan 2019 13:48:57 +0200 Subject: [PATCH 05/89] fix(repository): test for commit we are testing the commit procedure of CLI relation new command #2223 --- .idea/inspectionProfiles/Project_Default.xml | 9 + .idea/loopback-next.iml | 16 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 594 ++++++++++++++++ .../cli/generators/relation/ modelRelation.js | 639 ++++++++---------- .../generators/relation/controllerRelation.js | 246 +++---- packages/cli/generators/relation/index.js | 456 +++++++------ .../cli/generators/relation/relationutils.js | 5 + .../controller-relation-template.ts.ejs | 401 ++++++++--- 11 files changed, 1600 insertions(+), 787 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/loopback-next.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 packages/cli/generators/relation/relationutils.js diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000000..0e9c9caf0d63 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/.idea/loopback-next.iml b/.idea/loopback-next.iml new file mode 100644 index 000000000000..78ec2f3133f0 --- /dev/null +++ b/.idea/loopback-next.iml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000000..002b732944b0 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000000..5ddd6c9a8c37 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000000..35eb1ddfbbc0 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 000000000000..6f8de580c74e --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,594 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + false + true + true + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C:\Projects_IntelliJ - - - - - 1548673388767 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - No facets are configured - - - - - - - - - - - - - - - 1.8 - - - - - - - - loopback-next - - - - - - - - 1.8 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/cli/generators/relation/ modelRelation.js b/packages/cli/generators/relation/ modelRelation.js deleted file mode 100644 index 763d16b9eb67..000000000000 --- a/packages/cli/generators/relation/ modelRelation.js +++ /dev/null @@ -1,309 +0,0 @@ -const ArtifactGenerator = require('../../lib/artifact-generator'); - -const ast = require('ts-simple-ast'); -const path = require('path'); -const utils = require('../../lib/utils'); -const relationUtils = require('./relationutils'); - -module.exports = class RepositoryRelation extends ArtifactGenerator { - - constructor(args, opts) { - super(args, opts); - } - - _setupGenerator() { - - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir - }; - this.artifactInfo.repositoriesDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories' - ); - this.artifactInfo.modelsDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - } - - generateRelationRepository(sourceModel, targetModel, - foreignKey, relationName) { - this.initializeProperties(sourceModel, targetModel, relationName); - this.handleImports(); - this.handleProperties(); - this.handleConstructor(); - this.artifactInfo.srcRepositoryFile.save(); - } - - initializeProperties(sourceModel, targetModel, relationName) { - - this.artifactInfo.srcModelFile = path.resolve( - this.artifactInfo.modelsDir, - sourceModel + ".model.ts"); - - this.artifactInfo.dstModelFile = path.resolve( - this.artifactInfo.modelsDir, - targetModel + ".model.ts"); - - this.artifactInfo.srcModelClass = - this.getClassName(this.artifactInfo.srcModelFile); - - this.artifactInfo.dstModelClass = - this.getClassName(this.artifactInfo.dstModelFile); - - this.artifactInfo.srcRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - sourceModel + ".repository.ts" - ) - - this.artifactInfo.dstRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - targetModel + ".repository.ts" - ) - - this.artifactInfo.srcRepositoryClassName = - this.getClassName(this.artifactInfo.srcRepositoryFile); - - this.artifactInfo.dstRepositoryClassName = - this.getClassName(this.artifactInfo.dstRepositoryFile); - - this.artifactInfo.relationName = relationName; - - - this.artifactInfo.relationProperty = { - name: this.getRelationPropertyName(), - type: this.getRelationPropertyType() - } - - this.artifactInfo.srcRepositoryFile = new ast.Project(). - addExistingSourceFile(this.artifactInfo.srcRepositoryFile); - } - - getRelationPropertyName() { - let propertyName = this.artifactInfo.dstModelClass[0].toLowerCase(); - propertyName += this.artifactInfo.dstModelClass.substring(1); - - if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyName += "s"; - } - return propertyName; - } - - getRelationPropertyType() { - let propertyType = - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationUtils.relationType.belongsTo) { - propertyType += "Accessor"; - } - else if (this.artifactInfo.relationName == relationType.hasOne || - this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyType += "RepositoryFactory" - } - else { - throw Error("relation is invalid"); - } - propertyType = propertyType + - "<" + this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + - ", typeof " + - this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + - ".prototype.id>"; - - return (propertyType); - } - - getClassName(fileName) { - let sourceFile = new ast.Project().addExistingSourceFile(fileName); - let className = sourceFile.getClasses()[0].getNameOrThrow(); - return className; - } - - handleImports() { - let requierdImports = this.getRequiredImports(); - this.addRequiredImports(requierdImports); - } - - getRequiredImports() { - let importsArray = [{ - name: this.artifactInfo.dstModelClass, - module: "../models" - }, { - name: "repository", - module: "@loopback/repository" - }, { - name: "Getter", - module: "@loopback/core" - }, { - name: this.artifactInfo.dstRepositoryClassName, - module: "./index" - }]; - - let RelationName = - this.capitalizeFirstLetter(this.artifactInfo.relationName); - switch (this.artifactInfo.relationName) { - case (relationType.hasMany): - importsArray.push({ - name: RelationName + "RepositoryFactory", - module: "@loopback/repository" - }); - break; - case (relationType.hasOne): - importsArray.push({ - name: RelationName + "RepositoryFactory", - module: "@loopback/repository" - }); - break; - - case (relationType.belongsTo): - importsArray.push({ - name: RelationName + "Accessor", - module: "@loopback/repository" - }); - break; - - default: - result = false; - } - - return importsArray; - } - - addRequiredImports(requiredImports) { - for (let currentImport of requiredImports) { - this.addImport(currentImport, this.artifactInfo.srcRepositoryFile); - } - } - - addImport(requiredImport) { - if (!this.doesModuleExist(requiredImport)) { - this.addImportWithNonExistingModule(requiredImport); - } - else { - this.addImportsWithExistingModule(requiredImport); - } - } - - addImportWithNonExistingModule(requiredImport) { - this.artifactInfo.srcRepositoryFile.addImportDeclaration({ - moduleSpecifier: requiredImport.module, - namedImports: [requiredImport.name] - }); - } - - addImportsWithExistingModule(requiredImport) { - let moduleName = requiredImport.module; - let importDeclcaration = - this.artifactInfo.srcRepositoryFile. - getImportDeclarationOrThrow(moduleName); - if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { - importDeclcaration.addNamedImport(requiredImport.name); - } - } - - doesImportExist(importDelcaration, importName) { - let allNamedImports = importDelcaration.getNamedImports(); - for (let currentNamedImport of allNamedImports) { - if (currentNamedImport.getName() == importName) { - return true; - } - } - return false; - } - - doesModuleExist(importDeclaration) { - let moduleName = importDeclaration.module; - let relevantImport = this.artifactInfo.srcRepositoryFile. - getImportDeclaration(moduleName); - return (relevantImport != undefined); - } - - handleProperties() { - - let classDeclaration = - this.artifactInfo.srcRepositoryFile. - getClassOrThrow(this.artifactInfo.srcRepositoryClassName); - - this.addProperty(classDeclaration); - - this.orderProperties(classDeclaration); - } - - addProperty(classDeclaration) { - classDeclaration.addProperty({ - scope: ast.Scope.Public, - isReadonly: true, - name: this.artifactInfo.relationProperty.name, - type: this.artifactInfo.relationProperty.type, - }); - } - - orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function (currentProperty) { - currentProperty.setOrder(0); - }) - } - - handleConstructor() { - - let classDeclaration = - this.artifactInfo.srcRepositoryFile. - getClassOrThrow(this.artifactInfo.srcRepositoryClassName); - let classConstructor = classDeclaration.getConstructors()[0]; - - this.addParameters(classConstructor); - - this.addCreator(classConstructor); - } - - addParameters(classConstructor) { - classConstructor.addParameter({ - decorators: [{ - name: "repository.getter", - arguments: ["\'" + - this.artifactInfo.dstRepositoryClassName + "\'"] - }], - name: this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - "Getter", - type: "Getter<" + this.artifactInfo.dstRepositoryClassName + ">,", - scope: ast.Scope.Protected - }) - } - - - addCreator(classConstructor) { - let statement = "this.create" + - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationType.belongsTo) { - statement += "Accessor"; - } - else if (this.artifactInfo.relationName == relationType.hasMany || - this.artifactInfo.relationName == relationType.hasOne) { - statement += "RepositoryFactory"; - } - else { - throw Error("relation is invalid"); - } - statement += "For("; - - let parameter1 = "\'" + this.artifactInfo.relationProperty.name + "\',"; - let paramater2 = - this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - "Getter,"; - - statement = "this." + - this.artifactInfo.relationProperty.name + "=" + statement + - parameter1 + paramater2 + ");"; - - classConstructor.insertStatements(1, statement); - } - - capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); - } - - regularizeFirstLetter(string) { - return string.charAt(0).toLowerCase() + string.slice(1); - } - -} diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 66d49032ca03..4fea3b2508c4 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -15,141 +15,137 @@ const chalk = require('chalk'); const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); - const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template.ts.ejs'; // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { - // Note: arguments and options should be defined in the constructor. - constructor(args, opts) { - super(args, opts); - } - - _setupGenerator() { - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - this.artifactInfo.outDir = path.resolve( - this.artifactInfo.rootDir, - 'controllers', - ); - this.artifactInfo.modelDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - this.artifactInfo.repositoryDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories', - ); - - this.option('controllerType', { - type: String, - required: false, - description: 'Type for the ' + this.artifactInfo.type, - }); + // Note: arguments and options should be defined in the constructor. + constructor(args, opts) { + super(args, opts); + } + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.outDir = path.resolve( + this.artifactInfo.rootDir, + 'controllers', + ); + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + + this.option('controllerType', { + type: String, + required: false, + description: 'Type for the ' + this.artifactInfo.type, + }); + } + + scaffold() { + // We don't want to call the base scaffold function since it copies + // all of the templates! + // we can set here additional specific this.artifactInfo.xxx parameters if needed + + return; + } + + generateRelationController( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { + let project = new ast.Project(); + const sourceFile = this.addFileToProject( + project, + this.artifactInfo.modelDir + '/' + this.options.sourceModel + '.model.ts', + ); + this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); + + this.artifactInfo.foreignKey = foreignKey; + this.artifactInfo.keyType = this.options.keyType; + this.artifactInfo.repositoryVariableName = utils.camelCase( + sourceModel + 'Repo', + ); + this.artifactInfo.srcModel = utils.camelCase(sourceModel); + this.artifactInfo.dstModel = utils.camelCase(targetModel); + this.artifactInfo.modelName = utils.toClassName(targetModel); + this.artifactInfo.className = utils.toClassName( + this.artifactInfo.srcModel + this.artifactInfo.modelName, + ); + this.artifactInfo.repositoryName = utils.toClassName( + this.artifactInfo.srcModel + 'Repository', + ); + this.artifactInfo.relationModel = utils.toClassName( + this.artifactInfo.srcModel + '.' + this.artifactInfo.modelName, + ); + + this.artifactInfo.name = utils.camelCase(sourceModel + '-' + targetModel); + + this.artifactInfo.httpPathName = '/' + sourceModel + 's'; + if (this.shouldExit()) return false; + + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + if (debug.enabled) { + debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); } + // renames the file + let template = CONTROLLER_TEMPLATE_PATH; - scaffold() { - // We don't want to call the base scaffold function since it copies - // all of the templates! - // we can set here additional specific this.artifactInfo.xxx parameters if needed - - return; + const source = this.templatePath(template); + if (debug.enabled) { + debug('Using template at: ${source}'); } + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), + ); - generateRelationController(sourceModel, targetModel, foreignKey, relationName) { - - let project = new ast.Project(); - const sourceFile = this.addFileToProject(project, (this.artifactInfo.modelDir + "/" + this.options.sourceModel + ".model.ts")); - this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); - - - this.artifactInfo.foreignKey = foreignKey; - this.artifactInfo.keyType = this.options.keyType; - this.artifactInfo.repositoryVariableName = utils.camelCase( - (sourceModel + "Repo"), - ); - this.artifactInfo.srcModel = utils.camelCase( - sourceModel, - ); - this.artifactInfo.dstModel = utils.camelCase( - targetModel, - ); - this.artifactInfo.modelName = utils.toClassName( - targetModel, - ); - this.artifactInfo.className = utils.toClassName( - this.artifactInfo.srcModel + this.artifactInfo.modelName, - ); - this.artifactInfo.repositoryName = utils.toClassName( - (this.artifactInfo.srcModel + "Repository"), - ); - this.artifactInfo.relationModel = utils.toClassName( - this.artifactInfo.srcModel + "." + this.artifactInfo.modelName, - ); - - this.artifactInfo.name = utils.camelCase( - sourceModel + "-" + targetModel, - ); - - this.artifactInfo.httpPathName = "/" + sourceModel + "s"; - if (this.shouldExit()) return false; - - this.artifactInfo.outFile = - utils.kebabCase(this.artifactInfo.name) + - '.controller.ts'; - if (debug.enabled) { - debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); - } - - // renames the file - let template = CONTROLLER_TEMPLATE_PATH; - - const source = this.templatePath( - template - ); - if (debug.enabled) { - debug('Using template at: ${source}'); - } - const dest = this.destinationPath( - path.join(this.artifactInfo.outDir, this.artifactInfo.outFile) - ); - - if (debug.enabled) { - debug('artifactInfo: ${inspect(this.artifactInfo)}'); - debug('Copying artifact to: ${dest}'); - } - - - this.copyTemplatedFiles(source, dest, this.artifactInfo); - return; - + if (debug.enabled) { + debug('artifactInfo: ${inspect(this.artifactInfo)}'); + debug('Copying artifact to: ${dest}'); } - getKeyType(sourceFile, propertyName) { - const classObj = this.getClassObj(sourceFile); - if (classObj.getProperties().map(x => x.getName()).includes(propertyName)) { - return classObj.getProperty(propertyName).getType().getText(); - } + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; + } + + getKeyType(sourceFile, propertyName) { + const classObj = this.getClassObj(sourceFile); + if ( + classObj + .getProperties() + .map(x => x.getName()) + .includes(propertyName) + ) { + return classObj + .getProperty(propertyName) + .getType() + .getText(); } - - getClassObj(fileName) { - const className = fileName.getClasses()[0].getNameOrThrow(); - return fileName.getClassOrThrow(className); + } + + getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } + + addFileToProject(project, fileName) { + try { + return project.addExistingSourceFile(fileName); + } catch (e) { + throw new Error("source model file: '" + fileName + "' is not found."); } - - addFileToProject(project, fileName) { - try { - return project.addExistingSourceFile(fileName); - } catch (e) { - throw new Error("source model file: '" + fileName + "' is not found."); - } - } - -} - - + } +}; diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 15640d2b0a86..be75a6683d8b 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -2,10 +2,8 @@ // Node module: @loopback/cli // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -// Author: Raphael Drai at r.drai@f5.com 'use strict'; - const _ = require('lodash'); const ArtifactGenerator = require('../../lib/artifact-generator'); const debug = require('../../lib/debug')('relation-generator'); @@ -13,242 +11,292 @@ const inspect = require('util').inspect; const path = require('path'); const chalk = require('chalk'); const utils = require('../../lib/utils'); -const relationUtils = require('./relationutils'); +const tsquery = require('../../lib/ast-helper'); const ControllerRelation = require('./controllerRelation'); const RepositoryRelation = require('./repositoryRelation'); const ModelRelation = require('./modelRelation'); +const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; +const PROMPT_MESSAGE__SOURCE_MODEL = 'Please select source model'; +const PROMPT_MESSAGE__TARGET__MODEL = 'Please select target model'; +const availableRelationsBaseClasses = ['belongsTo', 'hasMany', 'hasOne']; const relPathControllersFolder = '/controllers'; const relPathModelsFolder = '/models'; const relPathRepositoriesFolder = '/repositories'; - let args; let opts; - - -// Exportable constants module.exports = class RelationGenerator extends ArtifactGenerator { - // Note: arguments and options should be defined in the constructor. - constructor(args, opts) { - super(args, opts); - this.args = args; - this.opts = opts; + constructor(args, opts) { + super(args, opts); + } + + /** + * get the property name for the id field + * @param {string} modelName + */ + async _getModelIdProperty(modelName) { + let fileContent = ''; + let modelFile = path.join( + this.artifactInfo.modelDir, + utils.getModelFileName(modelName), + ); + try { + fileContent = this.fs.read(modelFile, {}); + } catch (err) { + //debug(`${ERROR_READING_FILE} ${modelFile}: ${err.message}`); + return this.exit(err); } + return tsquery.getIdFromModel(fileContent); + } - _setupGenerator() { - super._setupGenerator(); - - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - // XXX(kjdelisle): These should be more extensible to allow custom paths - // for each artifact type. + async _scaffold() { + let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; + let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; + let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; - this.artifactInfo.outDir = path.resolve( - this.artifactInfo.rootDir, - '.', - ); + if (!this.options.relationType) { + throw new Error("'relationType' parameters should be specified."); } - - setOptions() { - return super.setOptions(); + if (this.options.sourceModel === this.options.destinationModel) { + throw new Error( + "'sourceModel' and 'destinationModel' parameter values should be different.", + ); } - checkLoopBackProject() { - if (this.shouldExit()) return; - return super.checkLoopBackProject(); + debug('Invoke Controller generator...'); + + let ctrl = new ControllerRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathCtrl; + if (this.options.relationType === 'belongsTo') { + ctrl.generateRelationController( + this.options.destinationModel, + this.options.sourceModel, + this.options.foreignKey, + this.options.relationType, + ); + } else { + ctrl.generateRelationController( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); } - promptArtifactName() { - debug('Prompting for artifact name'); - if (this.shouldExit()) return false; - // if (this.shouldExit()) return; - // return super.promptArtifactName(); + //Invoke here Model and Repository Generators + debug('Invoke Model generator...'); + let model = new ModelRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathModel; + model.generateRelationModel( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + + debug('Invoke Repository generator...'); + let repo = new RepositoryRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathRepo; + repo.generateRelationRepository( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + + return; + } + + _setupGenerator() { + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + outDir: utils.sourceRootDir, + }; + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + utils.modelsDir, + ); + } + + // Prompt a user for Relation type + async promptRelationBaseClassName() { + this.artifactInfo.relationType = await this.prompt([ + { + type: 'list', + name: 'relationBaseClass', + message: PROMPT_BASE_RELATION_CLASS, + choices: availableRelationsBaseClasses, + when: !this.artifactInfo.availableRelationsBaseClasses, + validate: utils.validateClassName, + }, + ]); + this.options.relationType = this.artifactInfo.relationType; + return this.artifactInfo.relationType; + } + + //Get model list for source model + async promptSourceModels() { + if (this.shouldExit()) return false; + + let modelList; + try { + //debug(`model list dir ${this.artifactInfo.modelDir}`); + modelList = await utils.getArtifactList( + this.artifactInfo.modelDir, + 'model', + ); + } catch (err) { + return this.exit(err); } + if (this.options.model) { + // debug(`Model name received from command line: ${this.options.model}`); + + this.options.model = utils.toClassName(this.options.model); + // assign the model name from the command line only if it is valid + if ( + modelList && + modelList.length > 0 && + modelList.includes(this.options.model) + ) { + Object.assign(this.artifactInfo, {modelNameList: [this.options.model]}); + } else { + modelList = []; + } + } + if (modelList.length === 0) { + return this.exit( + new Error( + `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. + ${chalk.yellow( + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, + ), + ); + } - /* - async promptArtifactCrudVars() { - if (this.shouldExit()) return; - - let modelList, repositoryList; - - try { - modelList = await utils.getArtifactList( - this.artifactInfo.modelDir, - 'model', - ); - - repositoryList = await utils.getArtifactList( - this.artifactInfo.repositoryDir, - 'repository', - true, - ); - } catch (err) { - return this.exit(err); - } - - if (_.isEmpty(modelList)) { - return this.exit( - `No models found in ${this.artifactInfo.modelDir}. - ${chalk.yellow( - 'Please visit http://loopback.io/doc/en/lb4/Controller-generator.html for information on how models are discovered', - )}`, - ); - } - if (_.isEmpty(repositoryList)) { - return this.exit( - `No repositories found in ${this.artifactInfo.repositoryDir}. - ${chalk.yellow( - 'Please visit http://loopback.io/doc/en/lb4/Controller-generator.html for information on how repositories are discovered', - )}`, - ); - } - return this.prompt([ - { - type: 'list', - name: 'modelName', - message: - 'What is the name of the model to use with this CRUD repository?', - choices: modelList, - when: this.artifactInfo.modelName === undefined, - default: modelList[0], - validate: utils.validateClassName, - }, - { - type: 'list', - name: 'repositoryName', - message: 'What is the name of your CRUD repository?', - choices: repositoryList, - when: this.artifactInfo.repositoryName === undefined, - default: repositoryList[0], - validate: utils.validateClassName, - }, - { - type: 'list', - name: 'idType', - message: 'What is the type of your ID?', - choices: ['number', 'string', 'object'], - when: this.artifactInfo.idType === undefined, - default: 'number', - }, - { - type: 'input', - name: 'httpPathName', - message: 'What is the base HTTP path name of the CRUD operations?', - when: this.artifactInfo.httpPathName === undefined, - default: answers => - utils.prependBackslash( - utils.pluralize(utils.urlSlug(answers.modelName)), - ), - validate: utils.validateUrlSlug, - filter: utils.prependBackslash, - }, - ]) - .then(props => { - debug(`props: ${inspect(props)}`); - Object.assign(this.artifactInfo, props); - // Ensure that the artifact names are valid. - [ - this.artifactInfo.name, - this.artifactInfo.modelName, - this.artifactInfo.repositoryName, - ].forEach(item => { - item = utils.toClassName(item); - }); - // Create camel-case names for variables. - this.artifactInfo.repositoryNameCamel = utils.camelCase( - this.artifactInfo.repositoryName, - ); - return props; - }) - .catch(err => { - debug(`Error during prompt for controller variables: ${err}`); - return this.exit(err); - }); - } - */ - - - - scaffold() { - // We don't want to call the base scaffold function since it copies - // all of the templates! - /* - Input parameters handling of CLI commmand "lb4 relation" that include the following options: - 1) provide as input a json file that contains the relation parameters: - $ lb4 relation --config file.json - where the content of json file is: - { - "sourceModel": "todo-list", - "destinationModel": "todo", - "relationType": "hasMany", - "foreignKey": "id" - } - 2) provide as input a string in json format (no file) - $ lb4 relation --config "{ \"sourceModel\": \"todo-list\",\"destinationModel\": \"todo\", - \"relationType\": \"hasMany\",\"foreignKey\": \"id\" }" - */ - - let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; - let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; - let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; - - if (!this.validateRelation(this.options.relationType)) { - throw new Error("'relationType' parameters should be specified."); - } - if (this.options.sourceModel === this.options.destinationModel) { - throw new Error("'sourceModel' and 'destinationModel' parameter values should be different."); - } - - - debug('Invoke Controller generator...'); - - let ctrl = new ControllerRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathCtrl; - if (this.options.relationType === relationUtils.relationType.belongsTo) { - ctrl.generateRelationController(this.options.destinationModel, this.options.sourceModel, - this.options.foreignKey, this.options.relationType); - } else { - ctrl.generateRelationController(this.options.sourceModel, this.options.destinationModel, - this.options.foreignKey, this.options.relationType); - } + // Prompt a user for source model + this.artifactInfo.sourceModel = await this.prompt([ + { + type: 'list', + name: 'modelNameList', + message: PROMPT_MESSAGE__SOURCE_MODEL, + choices: modelList, + when: this.artifactInfo.modelNameList === undefined, + }, + ]); + this.options.sourceModel = this.artifactInfo.sourceModel; + return this.artifactInfo.sourceModel; + } + + //Get model list for target model + async promptTargetModels() { + if (this.shouldExit()) return false; + + let modelList; + try { + //debug(`model list dir ${this.artifactInfo.modelDir}`); + modelList = await utils.getArtifactList( + this.artifactInfo.modelDir, + 'model', + ); + } catch (err) { + return this.exit(err); + } - //Invoke here Model and Repository Generators - debug('Invoke Model generator...'); - let model = new ModelRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathModel; - model.generateRelationModel(this.options.sourceModel, this.options.destinationModel, - this.options.foreignKey, this.options.relationType); - - debug('Invoke Repository generator...'); - let repo = new RepositoryRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathRepo; - repo.generateRelationRepository(this.options.sourceModel, this.options.destinationModel, - this.options.foreignKey, this.options.relationType); - - return; + if (this.options.model) { + // debug(`Model name received from command line: ${this.options.model}`); + + this.options.model = utils.toClassName(this.options.model); + // assign the model name from the command line only if it is valid + if ( + modelList && + modelList.length > 0 && + modelList.includes(this.options.model) + ) { + Object.assign(this.artifactInfo, {modelNameList: [this.options.model]}); + } else { + modelList = []; + } + } + if (modelList.length === 0) { + return this.exit( + new Error( + `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. + ${chalk.yellow( + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, + ), + ); } + // Prompt a user for a target model + this.artifactInfo.targetModel = await this.prompt([ + { + type: 'list', + name: 'modelNameList', + message: PROMPT_MESSAGE__TARGET__MODEL, + choices: modelList, + when: this.artifactInfo.modelNameList === undefined, + }, + ]); + this.options.destinationModel = this.artifactInfo.targetModel; + return this.artifactInfo.targetModel; + } + + //Prompt ID + async promptModelId() { + if (this.shouldExit()) return false; + let idProperty; + + debug(`Model ID property name from command line: ${this.options.id}`); + debug(`Selected Models: ${this.artifactInfo.sourceModel}`); + + if (_.isEmpty(this.artifactInfo.sourceModel)) { + return this.exit(new Error(`${ERROR_NO_MODEL_SELECTED}`)); + } else { + const prompts = [ + { + type: 'input', + name: 'propertyName', + message: `Please enter the name of the ID property for ${ + this.artifactInfo.sourceModel + }:`, + default: 'id', + }, + ]; + + // user supplied the id from the command line + if (this.options.id) { + debug(`passing thru this.options.id with value : ${this.options.id}`); + + idProperty = this.options.id; + /** make sure it is only used once, in case user selected more + * than one model. + */ + delete this.options.id; + } else { + idProperty = await this._getModelIdProperty( + this.artifactInfo.sourceModel.modelNameList, + ); - validateRelation(relationName) { - if (relationName != relationUtils.relationType.hasOne && - relationName != relationUtils.relationType.hasMany && - relationName != relationUtils.relationType.belongsTo) { - return false; + if (idProperty === null) { + const answer = await this.prompt(prompts); + idProperty = answer.propertyName; } - return true; - } - async end() { - await super.end(); + } + this.options.foreignKey = idProperty; + //Generate this repository + await this._scaffold(); + //} } + } }; diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js new file mode 100644 index 000000000000..5ade0f456c6b --- /dev/null +++ b/packages/cli/generators/relation/modelRelation.js @@ -0,0 +1,331 @@ +const ArtifactGenerator = require('../../lib/artifact-generator'); + +const ast = require('ts-simple-ast'); +const path = require('path'); +const utils = require('../../lib/utils'); +const relationUtils = require('./relationutils'); + +module.exports = class RepositoryRelation extends ArtifactGenerator { + constructor(args, opts) { + super(args, opts); + } + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + this.artifactInfo.repositoriesDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + this.artifactInfo.modelsDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + } + + generateRelationRepository( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { + this.initializeProperties(sourceModel, targetModel, relationName); + this.handleImports(); + this.handleProperties(); + this.handleConstructor(); + this.artifactInfo.srcRepositoryFile.save(); + } + + initializeProperties(sourceModel, targetModel, relationName) { + this.artifactInfo.srcModelFile = path.resolve( + this.artifactInfo.modelsDir, + sourceModel + '.model.ts', + ); + + this.artifactInfo.dstModelFile = path.resolve( + this.artifactInfo.modelsDir, + targetModel + '.model.ts', + ); + + this.artifactInfo.srcModelClass = this.getClassName( + this.artifactInfo.srcModelFile, + ); + + this.artifactInfo.dstModelClass = this.getClassName( + this.artifactInfo.dstModelFile, + ); + + this.artifactInfo.srcRepositoryFile = path.resolve( + this.artifactInfo.repositoriesDir, + sourceModel + '.repository.ts', + ); + + this.artifactInfo.dstRepositoryFile = path.resolve( + this.artifactInfo.repositoriesDir, + targetModel + '.repository.ts', + ); + + this.artifactInfo.srcRepositoryClassName = this.getClassName( + this.artifactInfo.srcRepositoryFile, + ); + + this.artifactInfo.dstRepositoryClassName = this.getClassName( + this.artifactInfo.dstRepositoryFile, + ); + + this.artifactInfo.relationName = relationName; + + this.artifactInfo.relationProperty = { + name: this.getRelationPropertyName(), + type: this.getRelationPropertyType(), + }; + + this.artifactInfo.srcRepositoryFile = new ast.Project().addExistingSourceFile( + this.artifactInfo.srcRepositoryFile, + ); + } + + getRelationPropertyName() { + let propertyName = this.artifactInfo.dstModelClass[0].toLowerCase(); + propertyName += this.artifactInfo.dstModelClass.substring(1); + + if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { + propertyName += 's'; + } + return propertyName; + } + + getRelationPropertyType() { + let propertyType = this.capitalizeFirstLetter( + this.artifactInfo.relationName, + ); + if ( + this.artifactInfo.relationName == relationUtils.relationType.belongsTo + ) { + propertyType += 'Accessor'; + } else if ( + this.artifactInfo.relationName == relationType.hasOne || + this.artifactInfo.relationName == relationUtils.relationType.hasMany + ) { + propertyType += 'RepositoryFactory'; + } else { + throw Error('relation is invalid'); + } + propertyType = + propertyType + + '<' + + this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + + ', typeof ' + + this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + + '.prototype.id>'; + + return propertyType; + } + + getClassName(fileName) { + let sourceFile = new ast.Project().addExistingSourceFile(fileName); + let className = sourceFile.getClasses()[0].getNameOrThrow(); + return className; + } + + handleImports() { + let requierdImports = this.getRequiredImports(); + this.addRequiredImports(requierdImports); + } + + getRequiredImports() { + let importsArray = [ + { + name: this.artifactInfo.dstModelClass, + module: '../models', + }, + { + name: 'repository', + module: '@loopback/repository', + }, + { + name: 'Getter', + module: '@loopback/core', + }, + { + name: this.artifactInfo.dstRepositoryClassName, + module: './index', + }, + ]; + + let RelationName = this.capitalizeFirstLetter( + this.artifactInfo.relationName, + ); + switch (this.artifactInfo.relationName) { + case relationType.hasMany: + importsArray.push({ + name: RelationName + 'RepositoryFactory', + module: '@loopback/repository', + }); + break; + case relationType.hasOne: + importsArray.push({ + name: RelationName + 'RepositoryFactory', + module: '@loopback/repository', + }); + break; + + case relationType.belongsTo: + importsArray.push({ + name: RelationName + 'Accessor', + module: '@loopback/repository', + }); + break; + + default: + result = false; + } + + return importsArray; + } + + addRequiredImports(requiredImports) { + for (let currentImport of requiredImports) { + this.addImport(currentImport, this.artifactInfo.srcRepositoryFile); + } + } + + addImport(requiredImport) { + if (!this.doesModuleExist(requiredImport)) { + this.addImportWithNonExistingModule(requiredImport); + } else { + this.addImportsWithExistingModule(requiredImport); + } + } + + addImportWithNonExistingModule(requiredImport) { + this.artifactInfo.srcRepositoryFile.addImportDeclaration({ + moduleSpecifier: requiredImport.module, + namedImports: [requiredImport.name], + }); + } + + addImportsWithExistingModule(requiredImport) { + let moduleName = requiredImport.module; + let importDeclcaration = this.artifactInfo.srcRepositoryFile.getImportDeclarationOrThrow( + moduleName, + ); + if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { + importDeclcaration.addNamedImport(requiredImport.name); + } + } + + doesImportExist(importDelcaration, importName) { + let allNamedImports = importDelcaration.getNamedImports(); + for (let currentNamedImport of allNamedImports) { + if (currentNamedImport.getName() == importName) { + return true; + } + } + return false; + } + + doesModuleExist(importDeclaration) { + let moduleName = importDeclaration.module; + let relevantImport = this.artifactInfo.srcRepositoryFile.getImportDeclaration( + moduleName, + ); + return relevantImport != undefined; + } + + handleProperties() { + let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( + this.artifactInfo.srcRepositoryClassName, + ); + + this.addProperty(classDeclaration); + + this.orderProperties(classDeclaration); + } + + addProperty(classDeclaration) { + classDeclaration.addProperty({ + scope: ast.Scope.Public, + isReadonly: true, + name: this.artifactInfo.relationProperty.name, + type: this.artifactInfo.relationProperty.type, + }); + } + + orderProperties(classDeclaration) { + classDeclaration.getProperties().forEach(function(currentProperty) { + currentProperty.setOrder(0); + }); + } + + handleConstructor() { + let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( + this.artifactInfo.srcRepositoryClassName, + ); + let classConstructor = classDeclaration.getConstructors()[0]; + + this.addParameters(classConstructor); + + this.addCreator(classConstructor); + } + + addParameters(classConstructor) { + classConstructor.addParameter({ + decorators: [ + { + name: 'repository.getter', + arguments: ["'" + this.artifactInfo.dstRepositoryClassName + "'"], + }, + ], + name: + this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + + 'Getter', + type: 'Getter<' + this.artifactInfo.dstRepositoryClassName + '>,', + scope: ast.Scope.Protected, + }); + } + + addCreator(classConstructor) { + let statement = + 'this.create' + + this.capitalizeFirstLetter(this.artifactInfo.relationName); + if (this.artifactInfo.relationName == relationType.belongsTo) { + statement += 'Accessor'; + } else if ( + this.artifactInfo.relationName == relationType.hasMany || + this.artifactInfo.relationName == relationType.hasOne + ) { + statement += 'RepositoryFactory'; + } else { + throw Error('relation is invalid'); + } + statement += 'For('; + + let parameter1 = "'" + this.artifactInfo.relationProperty.name + "',"; + let paramater2 = + this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + + 'Getter,'; + + statement = + 'this.' + + this.artifactInfo.relationProperty.name + + '=' + + statement + + parameter1 + + paramater2 + + ');'; + + classConstructor.insertStatements(1, statement); + } + + capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + + regularizeFirstLetter(string) { + return string.charAt(0).toLowerCase() + string.slice(1); + } +}; diff --git a/packages/cli/generators/relation/relationutils.js b/packages/cli/generators/relation/relationutils.js index b78f7f26dd34..10ce7dc274a5 100644 --- a/packages/cli/generators/relation/relationutils.js +++ b/packages/cli/generators/relation/relationutils.js @@ -1,5 +1,5 @@ exports.relationType = relationType = { - hasOne: "hasOne", - hasMany: "hasMany", - belongsTo: "belongsTo", -}; \ No newline at end of file + hasOne: 'hasOne', + hasMany: 'hasMany', + belongsTo: 'belongsTo', +}; diff --git a/packages/cli/lib/cli.js b/packages/cli/lib/cli.js index 3f7e36c00cce..1d948659c575 100644 --- a/packages/cli/lib/cli.js +++ b/packages/cli/lib/cli.js @@ -32,7 +32,7 @@ function runCommand(env, opts, log, dryRun) { } debug('invoking generator', args); // `yo` is adding flags converted to CamelCase - const options = camelCaseKeys(opts, { exclude: ['--', /^\w$/, 'argv'] }); + const options = camelCaseKeys(opts, {exclude: ['--', /^\w$/, 'argv']}); Object.assign(options, opts); debug('env.run %j %j', args, options); if (!dryRun) { diff --git a/packages/cli/package.json b/packages/cli/package.json index 8e743ac56b00..b8b981c26e3f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,6 +56,7 @@ "semver": "^5.5.0", "swagger-parser": "^5.0.0", "swagger2openapi": "^3.2.10", + "ts-simple-ast": "^21.0.2", "typescript": "^3.1.1", "unicode-10.0.0": "^0.7.4", "update-notifier": "^2.5.0", diff --git a/packages/cli/test/integration/cli/cli.integration.js b/packages/cli/test/integration/cli/cli.integration.js index eb81cfd74870..13005af50227 100644 --- a/packages/cli/test/integration/cli/cli.integration.js +++ b/packages/cli/test/integration/cli/cli.integration.js @@ -11,7 +11,7 @@ const main = require('../../../lib/cli'); function getLog(buffer) { buffer = buffer || []; - return function (format, ...params) { + return function(format, ...params) { buffer.push(util.format(format, ...params)); return buffer; }; @@ -20,18 +20,18 @@ function getLog(buffer) { describe('cli', () => { it('lists available commands', () => { const entries = []; - main({ commands: true }, getLog(entries)); + main({commands: true}, getLog(entries)); expect(entries).to.eql([ 'Available commands: ', ' lb4 app\n lb4 extension\n lb4 controller\n lb4 datasource\n ' + - 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n ' + - 'lb4 openapi\n lb4 relation', + 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n ' + + 'lb4 openapi\n lb4 relation', ]); }); it('lists versions', () => { const entries = []; - main({ version: true }, getLog(entries)); + main({version: true}, getLog(entries)); const logs = entries.join(''); expect(logs).to.match(/@loopback\/cli version\:/); expect(logs).to.match(/@loopback\/\* dependencies:/); @@ -39,18 +39,18 @@ describe('cli', () => { it('prints commands with --help', () => { const entries = []; - main({ help: true, _: [] }, getLog(entries), true); + main({help: true, _: []}, getLog(entries), true); expect(entries).to.containEql('Available commands: '); expect(entries).to.containEql( ' lb4 app\n lb4 extension\n lb4 controller\n lb4 datasource\n ' + - 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n ' + - 'lb4 openapi\n lb4 relation', + 'lb4 model\n lb4 repository\n lb4 service\n lb4 example\n ' + + 'lb4 openapi\n lb4 relation', ); }); it('does not print commands with --help for a given command', () => { const entries = []; - main({ help: true, _: ['app'] }, getLog(entries), true); + main({help: true, _: ['app']}, getLog(entries), true); expect(entries).to.not.containEql('Available commands: '); }); }); diff --git a/packages/context/src/binding-decorator.ts b/packages/context/src/binding-decorator.ts index 3c2cf907f0e9..07c3ee350c3f 100644 --- a/packages/context/src/binding-decorator.ts +++ b/packages/context/src/binding-decorator.ts @@ -89,7 +89,7 @@ export namespace bind { */ export function provider( ...specs: BindingSpec[] - ): ((target: Constructor>) => void) { + ): (target: Constructor>) => void { return (target: Constructor>) => { if (!isProviderClass(target)) { throw new Error(`Target ${target} is not a Provider`); From dc16b014607a1f96004f277c9a454407fa14dbe8 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Thu, 31 Jan 2019 10:07:49 -0500 Subject: [PATCH 10/89] Updated this.args and this.opts into relation cli. --- packages/cli/generators/relation/index.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index be75a6683d8b..4b4657383166 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -32,6 +32,8 @@ let opts; module.exports = class RelationGenerator extends ArtifactGenerator { constructor(args, opts) { super(args, opts); + this.args = args; + this.opts = opts; } /** @@ -168,7 +170,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { modelList.length > 0 && modelList.includes(this.options.model) ) { - Object.assign(this.artifactInfo, {modelNameList: [this.options.model]}); + Object.assign(this.artifactInfo, { modelNameList: [this.options.model] }); } else { modelList = []; } @@ -178,8 +180,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -223,7 +225,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { modelList.length > 0 && modelList.includes(this.options.model) ) { - Object.assign(this.artifactInfo, {modelNameList: [this.options.model]}); + Object.assign(this.artifactInfo, { modelNameList: [this.options.model] }); } else { modelList = []; } @@ -233,8 +235,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -269,7 +271,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { name: 'propertyName', message: `Please enter the name of the ID property for ${ this.artifactInfo.sourceModel - }:`, + }:`, default: 'id', }, ]; From e6e7e4e26eb56efaab27922c1207d409e93a48d0 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Thu, 31 Jan 2019 10:53:35 -0500 Subject: [PATCH 11/89] Fixed in controllerRelation sourceFile. --- packages/cli/generators/relation/controllerRelation.js | 3 ++- packages/cli/generators/relation/index.js | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 4fea3b2508c4..a74e0902297a 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -68,8 +68,9 @@ module.exports = class ControllerRelation extends ArtifactGenerator { let project = new ast.Project(); const sourceFile = this.addFileToProject( project, - this.artifactInfo.modelDir + '/' + this.options.sourceModel + '.model.ts', + this.artifactInfo.modelDir + '/' + utils.getModelFileName(this.options.sourceModel), ); + this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); this.artifactInfo.foreignKey = foreignKey; diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 4b4657383166..ace44df04ef3 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -196,7 +196,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { when: this.artifactInfo.modelNameList === undefined, }, ]); - this.options.sourceModel = this.artifactInfo.sourceModel; + this.options.sourceModel = this.artifactInfo.sourceModel.modelNameList; return this.artifactInfo.sourceModel; } @@ -250,7 +250,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { when: this.artifactInfo.modelNameList === undefined, }, ]); - this.options.destinationModel = this.artifactInfo.targetModel; + this.options.destinationModel = this.artifactInfo.targetModel.modelNameList; return this.artifactInfo.targetModel; } From d2055d0338d8c2d0fca6adbebceeb83c7b3bf000 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Thu, 31 Jan 2019 11:48:16 -0500 Subject: [PATCH 12/89] Added to relation generator has many template. --- .../generators/relation/controllerRelation.js | 36 +- packages/cli/generators/relation/index.js | 89 ++--- ...ntroller-relation-template-has-many.ts.ejs | 89 +++++ .../controller-relation-template.ts.ejs | 310 ------------------ 4 files changed, 169 insertions(+), 355 deletions(-) create mode 100644 packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs delete mode 100644 packages/cli/generators/relation/templates/controller-relation-template.ts.ejs diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index a74e0902297a..85c89c8915fb 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -15,7 +15,9 @@ const chalk = require('chalk'); const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); -const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template-has-many.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_HAS_MANY = 'controller-relation-template-has-many.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_HAS_ONE = 'controller-relation-template-has-one.ts.ejs'; // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { @@ -59,7 +61,16 @@ module.exports = class ControllerRelation extends ArtifactGenerator { return; } - generateRelationController( + generateControllerRelationBelongsTo( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { + throw new Error('Not implemented'); + } + + generateControllerRelationHasMany( sourceModel, targetModel, foreignKey, @@ -73,14 +84,15 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); + //@todo: update relationPropertyName + this.artifactInfo.relationPropertyName = 'foo'; this.artifactInfo.foreignKey = foreignKey; this.artifactInfo.keyType = this.options.keyType; - this.artifactInfo.repositoryVariableName = utils.camelCase( - sourceModel + 'Repo', - ); - this.artifactInfo.srcModel = utils.camelCase(sourceModel); - this.artifactInfo.dstModel = utils.camelCase(targetModel); - this.artifactInfo.modelName = utils.toClassName(targetModel); + this.artifactInfo.sourceRepositoryName = sourceModel + 'Repo'; + this.artifactInfo.sourceClassName = sourceModel; + this.artifactInfo.targetClassName = targetModel; + this.artifactInfo.controllerClassName = this.artifactInfo.sourceClassName + this.artifactInfo.targetClassName; + this.artifactInfo.modelName = targetModel; this.artifactInfo.className = utils.toClassName( this.artifactInfo.srcModel + this.artifactInfo.modelName, ); @@ -122,6 +134,14 @@ module.exports = class ControllerRelation extends ArtifactGenerator { return; } + generateControllerRelationHasOne( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { + throw new Error('Not implemented'); + } getKeyType(sourceFile, propertyName) { const classObj = this.getClassObj(sourceFile); if ( diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index ace44df04ef3..c8d44e8a6ffd 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -75,45 +75,60 @@ module.exports = class RelationGenerator extends ArtifactGenerator { let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; - if (this.options.relationType === 'belongsTo') { - ctrl.generateRelationController( - this.options.destinationModel, - this.options.sourceModel, - this.options.foreignKey, - this.options.relationType, - ); - } else { - ctrl.generateRelationController( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); - } - //Invoke here Model and Repository Generators - debug('Invoke Model generator...'); - let model = new ModelRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathModel; - model.generateRelationModel( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); + switch (this.options.relationType) { + case 'belongsTo': + ctrl.generateControllerRelationBelongsTo( + this.options.destinationModel, + this.options.sourceModel, + this.options.foreignKey, + this.options.relationType, + ); + break; + case 'hasMany': + ctrl.generateControllerRelationHasMany( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + break; + case 'hasOne': + ctrl.generateControllerRelationHasOne( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + break; + default: + throw new Error('Incorrect Relation Type'); + } - debug('Invoke Repository generator...'); - let repo = new RepositoryRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathRepo; - repo.generateRelationRepository( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); + /* + //Invoke here Model and Repository Generators + debug('Invoke Model generator...'); + let model = new ModelRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathModel; + model.generateRelationModel( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + debug('Invoke Repository generator...'); + let repo = new RepositoryRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathRepo; + repo.generateRelationRepository( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + */ return; } @@ -141,7 +156,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { validate: utils.validateClassName, }, ]); - this.options.relationType = this.artifactInfo.relationType; + this.options.relationType = this.artifactInfo.relationType.relationBaseClass; return this.artifactInfo.relationType; } diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs new file mode 100644 index 000000000000..a17b4bd78070 --- /dev/null +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -0,0 +1,89 @@ +import { + Count, + CountSchema, + Filter, + repository, + Where, +} from '@loopback/repository'; +import { + del, + get, + getWhereSchemaFor, + param, + patch, + post, + requestBody, +} from '@loopback/rest'; +import {<%= targetClassName %>} from '../models'; +import {<%= sourceRepositoryName %>} from '../repositories'; + +export class <%= controllerClassName %>Controller { + constructor( + @repository({<%= sourceRepositoryName %>}) protected todoListRepo: <%= sourceRepositoryName %>, + ) {} + + @post('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + responses: { + '200': { + description: '<%= sourceClassName %>.<%= targetClassName %> model instance', + content: {'application/json': {schema: {'x-ts-type': Todo}}}, + }, + }, + }) + async create( + @param.path.number('id') id: number, + @requestBody() todo: Todo, + ): Promise { + return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).create(todo); + } + + @get('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + responses: { + '200': { + description: "Array of Todo's belonging to TodoList", + content: { + 'application/json': { + schema: {type: 'array', items: {'x-ts-type': Todo}}, + }, + }, + }, + }, + }) + async find( + @param.path.number('id') id: number, + @param.query.object('filter') filter?: Filter, + ): Promise { + return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).find(filter); + } + + @patch('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + responses: { + '200': { + description: '<%= sourceClassName %>.<%= targetClassName %> PATCH success count', + content: {'application/json': {schema: CountSchema}}, + }, + }, + }) + async patch( + @param.path.number('id') id: number, + @requestBody() todo: Partial, + @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, + ): Promise { + return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).patch(todo, where); + } + + @del('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + responses: { + '200': { + description: '<%= sourceClassName %>.<%= targetClassName %> DELETE success count', + content: {'application/json': {schema: CountSchema}}, + }, + }, + }) + async delete( + @param.path.number('id') id: number, + @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, + ): Promise { + return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).delete(where); + } +} diff --git a/packages/cli/generators/relation/templates/controller-relation-template.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template.ts.ejs deleted file mode 100644 index 21cf1a18c3fb..000000000000 --- a/packages/cli/generators/relation/templates/controller-relation-template.ts.ejs +++ /dev/null @@ -1,310 +0,0 @@ -const ArtifactGenerator = require('../../lib/artifact-generator'); - -const ast = require('ts-simple-ast'); -const path = require('path'); -const utils = require('../../lib/utils'); -const relationUtils = require('./relationutils'); - -module.exports = class RepositoryRelation extends ArtifactGenerator { - - constructor(args, opts) { - super(args, opts); - } - - _setupGenerator() { - - super._setupGenerator(); - - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir - }; - this.artifactInfo.repositoriesDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories' - ); - this.artifactInfo.modelsDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - } - - generateRelationRepository(sourceModel, targetModel, - foreignKey, relationName) { - this.initializeProperties(sourceModel, targetModel, relationName); - this.handleImports(); - this.handleProperties(); - this.handleConstructor(); - this.artifactInfo.srcRepositoryFile.save(); - } - - initializeProperties(sourceModel, targetModel, relationName) { - - this.artifactInfo.srcModelFile = path.resolve( - this.artifactInfo.modelsDir, - sourceModel + ".model.ts"); - - this.artifactInfo.dstModelFile = path.resolve( - this.artifactInfo.modelsDir, - targetModel + ".model.ts"); - - this.artifactInfo.srcModelClass = - this.getClassName(this.artifactInfo.srcModelFile); - - this.artifactInfo.dstModelClass = - this.getClassName(this.artifactInfo.dstModelFile); - - this.artifactInfo.srcRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - sourceModel + ".repository.ts" - ) - - this.artifactInfo.dstRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - targetModel + ".repository.ts" - ) - - this.artifactInfo.srcRepositoryClassName = - this.getClassName(this.artifactInfo.srcRepositoryFile); - - this.artifactInfo.dstRepositoryClassName = - this.getClassName(this.artifactInfo.dstRepositoryFile); - - this.artifactInfo.relationName = relationName; - - - this.artifactInfo.relationProperty = { - name: this.getRelationPropertyName(), - type: this.getRelationPropertyType() - } - - this.artifactInfo.srcRepositoryFile = new ast.Project(). - addExistingSourceFile(this.artifactInfo.srcRepositoryFile); - } - - getRelationPropertyName() { - let propertyName = this.artifactInfo.dstModelClass[0].toLowerCase(); - propertyName += this.artifactInfo.dstModelClass.substring(1); - - if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyName += "s"; - } - return propertyName; - } - - getRelationPropertyType() { - let propertyType = - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationUtils.relationType.belongsTo) { - propertyType += "Accessor"; - } - else if (this.artifactInfo.relationName == relationType.hasOne || - this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyType += "RepositoryFactory" - } - else { - throw Error("relation is invalid"); - } - propertyType = propertyType + - "<" + this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + - ", typeof " + - this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + - ".prototype.id>"; - - return (propertyType); - } - - getClassName(fileName) { - let sourceFile = new ast.Project().addExistingSourceFile(fileName); - let className = sourceFile.getClasses()[0].getNameOrThrow(); - return className; - } - - handleImports() { - let requierdImports = this.getRequiredImports(); - this.addRequiredImports(requierdImports); - } - - getRequiredImports() { - let importsArray = [{ - name: this.artifactInfo.dstModelClass, - module: "../models" - }, { - name: "repository", - module: "@loopback/repository" - }, { - name: "Getter", - module: "@loopback/core" - }, { - name: this.artifactInfo.dstRepositoryClassName, - module: "./index" - }]; - - let RelationName = - this.capitalizeFirstLetter(this.artifactInfo.relationName); - switch (this.artifactInfo.relationName) { - case (relationType.hasMany): - importsArray.push({ - name: RelationName + "RepositoryFactory", - module: "@loopback/repository" - }); - break; - case (relationType.hasOne): - importsArray.push({ - name: RelationName + "RepositoryFactory", - module: "@loopback/repository" - }); - break; - - case (relationType.belongsTo): - importsArray.push({ - name: RelationName + "Accessor", - module: "@loopback/repository" - }); - break; - - default: - result = false; - } - - return importsArray; - } - - addRequiredImports(requiredImports) { - for (let currentImport of requiredImports) { - this.addImport(currentImport, this.artifactInfo.srcRepositoryFile); - } - } - - addImport(requiredImport) { - if (!this.doesModuleExist(requiredImport)) { - this.addImportWithNonExistingModule(requiredImport); - } - else { - this.addImportsWithExistingModule(requiredImport); - } - } - - addImportWithNonExistingModule(requiredImport) { - this.artifactInfo.srcRepositoryFile.addImportDeclaration({ - moduleSpecifier: requiredImport.module, - namedImports: [requiredImport.name] - }); - } - - addImportsWithExistingModule(requiredImport) { - let moduleName = requiredImport.module; - let importDeclcaration = - this.artifactInfo.srcRepositoryFile. - getImportDeclarationOrThrow(moduleName); - if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { - importDeclcaration.addNamedImport(requiredImport.name); - } - } - - doesImportExist(importDelcaration, importName) { - let allNamedImports = importDelcaration.getNamedImports(); - for (let currentNamedImport of allNamedImports) { - if (currentNamedImport.getName() == importName) { - return true; - } - } - return false; - } - - doesModuleExist(importDeclaration) { - let moduleName = importDeclaration.module; - let relevantImport = this.artifactInfo.srcRepositoryFile. - getImportDeclaration(moduleName); - return (relevantImport != undefined); - } - - handleProperties() { - - let classDeclaration = - this.artifactInfo.srcRepositoryFile. - getClassOrThrow(this.artifactInfo.srcRepositoryClassName); - - this.addProperty(classDeclaration); - - this.orderProperties(classDeclaration); - } - - addProperty(classDeclaration) { - classDeclaration.addProperty({ - scope: ast.Scope.Public, - isReadonly: true, - name: this.artifactInfo.relationProperty.name, - type: this.artifactInfo.relationProperty.type, - }); - } - - orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function (currentProperty) { - currentProperty.setOrder(0); - }) - } - - handleConstructor() { - - let classDeclaration = - this.artifactInfo.srcRepositoryFile. - getClassOrThrow(this.artifactInfo.srcRepositoryClassName); - let classConstructor = classDeclaration.getConstructors()[0]; - - this.addParameters(classConstructor); - - this.addCreator(classConstructor); - } - - addParameters(classConstructor) { - classConstructor.addParameter({ - decorators: [{ - name: "repository.getter", - arguments: ["\'" + - this.artifactInfo.dstRepositoryClassName + "\'"] - }], - name: this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - "Getter", - type: "Getter<" + this.artifactInfo.dstRepositoryClassName + ">,", - scope: ast.Scope.Protected - }) - } - - - addCreator(classConstructor) { - let statement = "this.create" + - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationType.belongsTo) { - statement += "Accessor"; - } - else if (this.artifactInfo.relationName == relationType.hasMany || - this.artifactInfo.relationName == relationType.hasOne) { - statement += "RepositoryFactory"; - } - else { - throw Error("relation is invalid"); - } - statement += "For("; - - let parameter1 = "\'" + this.artifactInfo.relationProperty.name + "\',"; - let paramater2 = - this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - "Getter,"; - - statement = "this." + - this.artifactInfo.relationProperty.name + "=" + statement + - parameter1 + paramater2 + ");"; - - classConstructor.insertStatements(1, statement); - } - - capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); - } - - regularizeFirstLetter(string) { - return string.charAt(0).toLowerCase() + string.slice(1); - } - -} From 2880e9578c106cda91c6c1ffd7864cf8521982d8 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 3 Feb 2019 06:09:09 -0500 Subject: [PATCH 13/89] Replaced relation type to constants. --- packages/cli/generators/relation/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index c8d44e8a6ffd..f331f0269911 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -20,7 +20,16 @@ const ModelRelation = require('./modelRelation'); const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE__SOURCE_MODEL = 'Please select source model'; const PROMPT_MESSAGE__TARGET__MODEL = 'Please select target model'; -const availableRelationsBaseClasses = ['belongsTo', 'hasMany', 'hasOne']; + +const RELATION_TYPE_BELONGS_TO = 'belongsTo'; +const RELATION_TYPE_HAS_MANY = 'hasMany'; +const RELATION_TYPE_HAS_ONE = 'hasOne'; + +const availableRelationsBaseClasses = [ + RELATION_TYPE_BELONGS_TO, + RELATION_TYPE_HAS_MANY, + RELATION_TYPE_HAS_ONE +]; const relPathControllersFolder = '/controllers'; const relPathModelsFolder = '/models'; @@ -77,7 +86,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.artifactInfo.relPath = relPathCtrl; switch (this.options.relationType) { - case 'belongsTo': + case RELATION_TYPE_BELONGS_TO: ctrl.generateControllerRelationBelongsTo( this.options.destinationModel, this.options.sourceModel, @@ -85,7 +94,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.options.relationType, ); break; - case 'hasMany': + case RELATION_TYPE_HAS_MANY: ctrl.generateControllerRelationHasMany( this.options.sourceModel, this.options.destinationModel, @@ -93,7 +102,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.options.relationType, ); break; - case 'hasOne': + case RELATION_TYPE_HAS_ONE: ctrl.generateControllerRelationHasOne( this.options.sourceModel, this.options.destinationModel, From 807c455e850765e8027fefd8ec463373536eedbe Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 3 Feb 2019 06:32:18 -0500 Subject: [PATCH 14/89] Model selection was moved to separate function. --- packages/cli/generators/relation/index.js | 85 +++++++---------------- 1 file changed, 27 insertions(+), 58 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index f331f0269911..b0c86c91bcbf 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -169,13 +169,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return this.artifactInfo.relationType; } - //Get model list for source model - async promptSourceModels() { - if (this.shouldExit()) return false; - + async _promptModelList(message, parameter) { let modelList; try { - //debug(`model list dir ${this.artifactInfo.modelDir}`); + debug(`model list dir ${this.artifactInfo.modelDir}`); modelList = await utils.getArtifactList( this.artifactInfo.modelDir, 'model', @@ -184,17 +181,17 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return this.exit(err); } - if (this.options.model) { - // debug(`Model name received from command line: ${this.options.model}`); + if (this.options[parameter]) { + debug(`Model name received from command line: ${this.options[parameter]}`); - this.options.model = utils.toClassName(this.options.model); + this.options.model = utils.toClassName(this.options[parameter]); // assign the model name from the command line only if it is valid if ( modelList && modelList.length > 0 && modelList.includes(this.options.model) ) { - Object.assign(this.artifactInfo, { modelNameList: [this.options.model] }); + Object.assign(this.artifactInfo, { modelNameList: [this.options[parameter]] }); } else { modelList = []; } @@ -210,71 +207,43 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); } - // Prompt a user for source model - this.artifactInfo.sourceModel = await this.prompt([ + // Prompt a user for model. + return await this.prompt([ { type: 'list', name: 'modelNameList', - message: PROMPT_MESSAGE__SOURCE_MODEL, + message: message, choices: modelList, when: this.artifactInfo.modelNameList === undefined, }, ]); + } + + //Get model list for source model + async promptSourceModels() { + if (this.shouldExit()) return false; + + // Prompt a user for source model + this.artifactInfo.sourceModel = await this._promptModelList( + PROMPT_MESSAGE__SOURCE_MODEL, + 'sourceModel' + ); this.options.sourceModel = this.artifactInfo.sourceModel.modelNameList; + return this.artifactInfo.sourceModel; } //Get model list for target model async promptTargetModels() { if (this.shouldExit()) return false; - - let modelList; - try { - //debug(`model list dir ${this.artifactInfo.modelDir}`); - modelList = await utils.getArtifactList( - this.artifactInfo.modelDir, - 'model', - ); - } catch (err) { - return this.exit(err); - } - - if (this.options.model) { - // debug(`Model name received from command line: ${this.options.model}`); - - this.options.model = utils.toClassName(this.options.model); - // assign the model name from the command line only if it is valid - if ( - modelList && - modelList.length > 0 && - modelList.includes(this.options.model) - ) { - Object.assign(this.artifactInfo, { modelNameList: [this.options.model] }); - } else { - modelList = []; - } - } - if (modelList.length === 0) { - return this.exit( - new Error( - `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. - ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, - ), - ); - } // Prompt a user for a target model - this.artifactInfo.targetModel = await this.prompt([ - { - type: 'list', - name: 'modelNameList', - message: PROMPT_MESSAGE__TARGET__MODEL, - choices: modelList, - when: this.artifactInfo.modelNameList === undefined, - }, - ]); + + this.artifactInfo.targetModel = await this._promptModelList( + PROMPT_MESSAGE__TARGET__MODEL, + 'destinationModel' + ); this.options.destinationModel = this.artifactInfo.targetModel.modelNameList; + return this.artifactInfo.targetModel; } From 846930709c1ec60313d81b5d7221a33160d4b5dc Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 3 Feb 2019 06:42:05 -0500 Subject: [PATCH 15/89] Code refactoring. --- packages/cli/generators/relation/index.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index b0c86c91bcbf..2b40f3300b78 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -208,7 +208,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } // Prompt a user for model. - return await this.prompt([ + this.artifactInfo[parameter] = await this.prompt([ { type: 'list', name: 'modelNameList', @@ -217,34 +217,28 @@ module.exports = class RelationGenerator extends ArtifactGenerator { when: this.artifactInfo.modelNameList === undefined, }, ]); + this.options[parameter] = this.artifactInfo[parameter].modelNameList; + return this.artifactInfo[parameter]; } - //Get model list for source model + // Get model list for source model. async promptSourceModels() { if (this.shouldExit()) return false; - // Prompt a user for source model - this.artifactInfo.sourceModel = await this._promptModelList( + return await this._promptModelList( PROMPT_MESSAGE__SOURCE_MODEL, 'sourceModel' ); - this.options.sourceModel = this.artifactInfo.sourceModel.modelNameList; - - return this.artifactInfo.sourceModel; } - //Get model list for target model + // Get model list for target model. async promptTargetModels() { if (this.shouldExit()) return false; - // Prompt a user for a target model - this.artifactInfo.targetModel = await this._promptModelList( + return await this._promptModelList( PROMPT_MESSAGE__TARGET__MODEL, 'destinationModel' ); - this.options.destinationModel = this.artifactInfo.targetModel.modelNameList; - - return this.artifactInfo.targetModel; } //Prompt ID From 1056a2fe8f95c3fdd9005a29fcaf098f9ec9f7c9 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Tue, 5 Feb 2019 00:32:53 +0200 Subject: [PATCH 16/89] feat(cli): added belongsto controller and belongsto template --- .../generators/relation/controllerRelation.js | 65 ++++++++++++++++++- packages/cli/generators/relation/index.js | 2 +- .../belongsto-template.controller.ts.ejs | 23 +++++++ 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 85c89c8915fb..09ea6ea3d0f8 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -18,7 +18,7 @@ const ast = require('ts-simple-ast'); const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template-has-many.ts.ejs'; const CONTROLLER_TEMPLATE_PATH_HAS_MANY = 'controller-relation-template-has-many.ts.ejs'; const CONTROLLER_TEMPLATE_PATH_HAS_ONE = 'controller-relation-template-has-one.ts.ejs'; - +const CONTROLLER_TEMPLATE_PATH_BELONGSTO = 'belongsto-template.controller.ts.ejs'; // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { // Note: arguments and options should be defined in the constructor. @@ -67,9 +67,70 @@ module.exports = class ControllerRelation extends ArtifactGenerator { foreignKey, relationName, ) { - throw new Error('Not implemented'); + let project = new ast.Project(); + const sourceFile = this.addFileToProject( + project, + this.artifactInfo.modelDir + '/' + utils.getModelFileName(this.options.sourceModel), + ); + + this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); + + //@todo: update relationPropertyName + this.artifactInfo.relationPropertyName = 'memberId'; + this.artifactInfo.foreignKey = foreignKey; + this.artifactInfo.keyType = this.options.keyType; + this.artifactInfo.sourceRepositoryName = sourceModel + 'Repo'; + this.artifactInfo.sourceClassName = sourceModel; + this.artifactInfo.targetClassName = targetModel; + this.artifactInfo.sourceModelName = utils.lowerCase(sourceModel); + this.artifactInfo.targetModelName = utils.lowerCase(targetModel); + this.artifactInfo.controllerClassName = this.artifactInfo.sourceClassName + this.artifactInfo.targetClassName; + this.artifactInfo.modelName = targetModel; + this.artifactInfo.className = utils.toClassName( + this.artifactInfo.sourceClassName + this.artifactInfo.modelName, + ); + this.artifactInfo.repositoryName = utils.toClassName( + this.artifactInfo.sourceClassName + 'Repository', + ); + this.artifactInfo.repositoryProperty = utils.camelCase( + this.artifactInfo.sourceClassName + 'Repository', + ); + this.artifactInfo.relationModel = utils.toClassName( + this.artifactInfo.sourceClassName + '.' + this.artifactInfo.modelName, + ); + + this.artifactInfo.name = utils.camelCase(targetModel + '-mordi-' + sourceModel); + + this.artifactInfo.httpPathName = '/' + sourceModel + 's'; + if (this.shouldExit()) return false; + + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + if (debug.enabled) { + debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); + } + + // renames the file + let template = CONTROLLER_TEMPLATE_PATH_BELONGSTO; + + const source = this.templatePath(template); + if (debug.enabled) { + debug('Using template at: ${source}'); + } + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), + ); + + if (debug.enabled) { + debug('artifactInfo: ${inspect(this.artifactInfo)}'); + debug('Copying artifact to: ${dest}'); + } + + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; } + generateControllerRelationHasMany( sourceModel, targetModel, diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 2b40f3300b78..4592ed802bff 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -88,8 +88,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { switch (this.options.relationType) { case RELATION_TYPE_BELONGS_TO: ctrl.generateControllerRelationBelongsTo( - this.options.destinationModel, this.options.sourceModel, + this.options.destinationModel, this.options.foreignKey, this.options.relationType, ); diff --git a/packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs b/packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs new file mode 100644 index 000000000000..1ec9b4d65353 --- /dev/null +++ b/packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs @@ -0,0 +1,23 @@ +import { + repository, +} from '@loopback/repository'; +import { + param, + get, +} from '@loopback/rest'; +import { <%= sourceClassName %>, <%= targetClassName %> } from '../models'; +import { <%= repositoryName %> } from '../repositories'; + +export class <%= className %>Controller { + constructor( + @repository(<%= repositoryName %>) + public <%= repositoryProperty %>: <%= repositoryName %>, + ) { } + + @get('/<%= sourceModelName %>s/{id}/<%= targetModelName %>') + async get<%= targetClassName %>( + @param.path.number('id') <%= relationPropertyName %>: typeof <%= sourceClassName %>.prototype.id, + ): Promise<<%= targetClassName %>> { + return await this.<%= repositoryProperty %>.<%= targetModelName %>(<%= relationPropertyName %>); + } +} From 4f3835f9d85cb7d659f6c4489155643cca5ac2c9 Mon Sep 17 00:00:00 2001 From: Or Shlomo Date: Tue, 5 Feb 2019 10:29:13 +0200 Subject: [PATCH 17/89] docs(docs): initial documentation for the "lb4 relation" cli command --- docs/site/Relation-generator.md | 73 +++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/site/Relation-generator.md diff --git a/docs/site/Relation-generator.md b/docs/site/Relation-generator.md new file mode 100644 index 000000000000..46f8801bed02 --- /dev/null +++ b/docs/site/Relation-generator.md @@ -0,0 +1,73 @@ +--- +lang: en +title: 'Relation generator' +keywords: LoopBack 4.0, LoopBack 4 +sidebar: lb4_sidebar +permalink: /doc/en/lb4/Relation-generator.html +--- + +{% include content/generator-create-app.html lang=page.lang %} + +The models involved in the relation must also exist before running this +generator. + +### Synopsis + +Adds a new `Relation` between existing source and target models in a LoopBack +application. + +```sh +lb4 relation [options] [] +``` + +### Options + +TBD + +### Arguments + +`` - Name of the ID property in the source model to create as an +argument to the command. If provided, the tool will use that as the default when +it prompts for the ID property name. + +### Interactive Prompts + +The tool will prompt you for: + +- **Relation `type` between models.** _(relationBaseClass)_ Prompts a list of + available relations to choose from as the type of the relation between the + source model and the target model. Relation types supported: + + - hasMany + - hasOne + - belongsTo + +- **Name of the `source` model.** _(sourceModel)_ Prompts a list of available + models to choose from as the source model of the relation. + +- **Name of the `target` model.** _(targetModel)_ Prompts a list of available + models to choose from as the target model of the relation. Please note - this + will not let you choose the same model as the source model chosen. + +- **Name of the `ID property` in the source model.** _(Optional, default: `id`)_ + Prompts for the ID property name (serves as the foreign key) in the source + model. Leave blank for using default. + +- **Name for the `property relation` in the source model.** TBD + +### Output + +Once all the prompts have been answered, the CLI will do the following: + +- Update source Model class as follows: + `/src/models/${sourceModel-Name}.model.ts` +- Update target Model class as follows: + `/src/models/${targetModel-Name}.model.ts` +- Update source Model Repository class as follows: + `/src/repositories/${sourceModel-Repository-Name}.repository.ts` +- Update target Model Repository class as follows: + `/src/repositories/${targetModel-Repository-Name}.repository.ts` +- Create a Controller for the new relation as follows: + `/src/controllers/{sourceModel}-{targetModel}.controller.ts` +- Update `/src/controllers/index.ts` to export the newly created Controller + class. From 4771598d186795ce79575f9ebee8039d776f047e Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 5 Feb 2019 05:04:09 -0500 Subject: [PATCH 18/89] Fixed belongsTo controller. --- .../generators/relation/controllerRelation.js | 66 +++++-------------- .../belongsto-template.controller.ts.ejs | 23 ------- 2 files changed, 16 insertions(+), 73 deletions(-) delete mode 100644 packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 09ea6ea3d0f8..961450863b9a 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -15,10 +15,10 @@ const chalk = require('chalk'); const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); -const CONTROLLER_TEMPLATE_PATH = 'controller-relation-template-has-many.ts.ejs'; const CONTROLLER_TEMPLATE_PATH_HAS_MANY = 'controller-relation-template-has-many.ts.ejs'; const CONTROLLER_TEMPLATE_PATH_HAS_ONE = 'controller-relation-template-has-one.ts.ejs'; -const CONTROLLER_TEMPLATE_PATH_BELONGSTO = 'belongsto-template.controller.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_BELONGSTO = 'controller-relation-template-belongsto.ts.ejs'; + // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { // Note: arguments and options should be defined in the constructor. @@ -67,65 +67,31 @@ module.exports = class ControllerRelation extends ArtifactGenerator { foreignKey, relationName, ) { - let project = new ast.Project(); - const sourceFile = this.addFileToProject( - project, - this.artifactInfo.modelDir + '/' + utils.getModelFileName(this.options.sourceModel), + this.artifactInfo.sourceModelClassName = sourceModel; + this.artifactInfo.targetModelClassName = targetModel; + this.artifactInfo.sourceRepositoryClassName = + this.artifactInfo.sourceModelClassName + 'Repository'; + this.artifactInfo.controllerClassName = + this.artifactInfo.sourceModelClassName + this.artifactInfo.targetModelClassName + 'Controller'; + + this.artifactInfo.paramSourceRepository = utils.camelCase( + this.artifactInfo.sourceModelClassName + 'Repository', ); - this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); - - //@todo: update relationPropertyName - this.artifactInfo.relationPropertyName = 'memberId'; - this.artifactInfo.foreignKey = foreignKey; - this.artifactInfo.keyType = this.options.keyType; - this.artifactInfo.sourceRepositoryName = sourceModel + 'Repo'; - this.artifactInfo.sourceClassName = sourceModel; - this.artifactInfo.targetClassName = targetModel; this.artifactInfo.sourceModelName = utils.lowerCase(sourceModel); this.artifactInfo.targetModelName = utils.lowerCase(targetModel); - this.artifactInfo.controllerClassName = this.artifactInfo.sourceClassName + this.artifactInfo.targetClassName; - this.artifactInfo.modelName = targetModel; - this.artifactInfo.className = utils.toClassName( - this.artifactInfo.sourceClassName + this.artifactInfo.modelName, - ); - this.artifactInfo.repositoryName = utils.toClassName( - this.artifactInfo.sourceClassName + 'Repository', - ); - this.artifactInfo.repositoryProperty = utils.camelCase( - this.artifactInfo.sourceClassName + 'Repository', - ); - this.artifactInfo.relationModel = utils.toClassName( - this.artifactInfo.sourceClassName + '.' + this.artifactInfo.modelName, - ); - - this.artifactInfo.name = utils.camelCase(targetModel + '-mordi-' + sourceModel); - this.artifactInfo.httpPathName = '/' + sourceModel + 's'; - if (this.shouldExit()) return false; + this.artifactInfo.relationPropertyName = 'fooo'; - this.artifactInfo.outFile = - utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; - if (debug.enabled) { - debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); - } + const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGSTO); - // renames the file - let template = CONTROLLER_TEMPLATE_PATH_BELONGSTO; + this.artifactInfo.name = sourceModel + '-' + targetModel; + this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; - const source = this.templatePath(template); - if (debug.enabled) { - debug('Using template at: ${source}'); - } const dest = this.destinationPath( path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), ); - if (debug.enabled) { - debug('artifactInfo: ${inspect(this.artifactInfo)}'); - debug('Copying artifact to: ${dest}'); - } - this.copyTemplatedFiles(source, dest, this.artifactInfo); return; } @@ -176,7 +142,7 @@ module.exports = class ControllerRelation extends ArtifactGenerator { } // renames the file - let template = CONTROLLER_TEMPLATE_PATH; + let template = CONTROLLER_TEMPLATE_PATH_HAS_MANY; const source = this.templatePath(template); if (debug.enabled) { diff --git a/packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs b/packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs deleted file mode 100644 index 1ec9b4d65353..000000000000 --- a/packages/cli/generators/relation/templates/belongsto-template.controller.ts.ejs +++ /dev/null @@ -1,23 +0,0 @@ -import { - repository, -} from '@loopback/repository'; -import { - param, - get, -} from '@loopback/rest'; -import { <%= sourceClassName %>, <%= targetClassName %> } from '../models'; -import { <%= repositoryName %> } from '../repositories'; - -export class <%= className %>Controller { - constructor( - @repository(<%= repositoryName %>) - public <%= repositoryProperty %>: <%= repositoryName %>, - ) { } - - @get('/<%= sourceModelName %>s/{id}/<%= targetModelName %>') - async get<%= targetClassName %>( - @param.path.number('id') <%= relationPropertyName %>: typeof <%= sourceClassName %>.prototype.id, - ): Promise<<%= targetClassName %>> { - return await this.<%= repositoryProperty %>.<%= targetModelName %>(<%= relationPropertyName %>); - } -} From 35d70e732f7cd9a7f4b78d09e47213f586a4be7f Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 5 Feb 2019 05:04:39 -0500 Subject: [PATCH 19/89] Fixed belongsTo controller. --- ...troller-relation-template-belongsto.ts.ejs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs diff --git a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs new file mode 100644 index 000000000000..9edc6c0ae66a --- /dev/null +++ b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs @@ -0,0 +1,23 @@ +import { + repository, +} from '@loopback/repository'; +import { + param, + get, +} from '@loopback/rest'; +import { <%= sourceModelClassName %>, <%= targetModelClassName %> } from '../models'; +import { <%= sourceRepositoryClassName %> } from '../repositories'; + +export class <%= controllerClassName %> { + constructor( + @repository(<%= sourceRepositoryClassName %>) + public <%= paramSourceRepository %>: <%= sourceRepositoryClassName %>, + ) { } + + @get('/<%= sourceModelName %>s/{id}/<%= targetModelName %>') + async get<%= targetModelClassName %>( + @param.path.number('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.id, + ): Promise<<%= targetModelClassName %>> { + return await this.<%= paramSourceRepository %>.<%= targetModelName %>(<%= relationPropertyName %>); + } +} From be05f016283bf9410d9cd9e89fca4a6e91bb5367 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 5 Feb 2019 06:28:23 -0500 Subject: [PATCH 20/89] Fixed has many controller tempalte. --- .../generators/relation/controllerRelation.js | 62 ++++------------ ...ntroller-relation-template-has-many.ts.ejs | 70 +++++++++---------- 2 files changed, 50 insertions(+), 82 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 961450863b9a..12d8abeaf91c 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -78,8 +78,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceModelClassName + 'Repository', ); - this.artifactInfo.sourceModelName = utils.lowerCase(sourceModel); - this.artifactInfo.targetModelName = utils.lowerCase(targetModel); + this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); + this.artifactInfo.targetModelName = utils.kebabCase(targetModel); this.artifactInfo.relationPropertyName = 'fooo'; @@ -103,60 +103,28 @@ module.exports = class ControllerRelation extends ArtifactGenerator { foreignKey, relationName, ) { - let project = new ast.Project(); - const sourceFile = this.addFileToProject( - project, - this.artifactInfo.modelDir + '/' + utils.getModelFileName(this.options.sourceModel), - ); - - this.options.keyType = this.getKeyType(sourceFile, this.options.foreignKey); - - //@todo: update relationPropertyName - this.artifactInfo.relationPropertyName = 'foo'; - this.artifactInfo.foreignKey = foreignKey; - this.artifactInfo.keyType = this.options.keyType; - this.artifactInfo.sourceRepositoryName = sourceModel + 'Repo'; - this.artifactInfo.sourceClassName = sourceModel; - this.artifactInfo.targetClassName = targetModel; - this.artifactInfo.controllerClassName = this.artifactInfo.sourceClassName + this.artifactInfo.targetClassName; - this.artifactInfo.modelName = targetModel; - this.artifactInfo.className = utils.toClassName( - this.artifactInfo.srcModel + this.artifactInfo.modelName, - ); - this.artifactInfo.repositoryName = utils.toClassName( - this.artifactInfo.srcModel + 'Repository', - ); - this.artifactInfo.relationModel = utils.toClassName( - this.artifactInfo.srcModel + '.' + this.artifactInfo.modelName, + this.artifactInfo.sourceModelClassName = sourceModel; + this.artifactInfo.targetModelClassName = targetModel; + this.artifactInfo.sourceRepositoryClassName = + this.artifactInfo.sourceModelClassName + 'Repository'; + this.artifactInfo.controllerClassName = + this.artifactInfo.sourceModelClassName + this.artifactInfo.targetModelClassName + 'Controller'; + this.artifactInfo.paramSourceRepository = utils.camelCase( + this.artifactInfo.sourceModelClassName + 'Repository', ); - this.artifactInfo.name = utils.camelCase(sourceModel + '-' + targetModel); + this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); + this.artifactInfo.targetModelName = utils.kebabCase(targetModel); - this.artifactInfo.httpPathName = '/' + sourceModel + 's'; - if (this.shouldExit()) return false; + const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); - this.artifactInfo.outFile = - utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; - if (debug.enabled) { - debug('Artifact output filename set to: ${this.artifactInfo.outFile}'); - } - - // renames the file - let template = CONTROLLER_TEMPLATE_PATH_HAS_MANY; + this.artifactInfo.name = sourceModel + '-' + targetModel; + this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; - const source = this.templatePath(template); - if (debug.enabled) { - debug('Using template at: ${source}'); - } const dest = this.destinationPath( path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), ); - if (debug.enabled) { - debug('artifactInfo: ${inspect(this.artifactInfo)}'); - debug('Copying artifact to: ${dest}'); - } - this.copyTemplatedFiles(source, dest, this.artifactInfo); return; } diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs index a17b4bd78070..35954e78d91a 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -14,76 +14,76 @@ import { post, requestBody, } from '@loopback/rest'; -import {<%= targetClassName %>} from '../models'; -import {<%= sourceRepositoryName %>} from '../repositories'; +import { <%= sourceModelClassName %>, <%= targetModelClassName %> } from '../models'; +import { <%= sourceRepositoryClassName %> } from '../repositories'; -export class <%= controllerClassName %>Controller { +export class <%= controllerClassName %> { constructor( - @repository({<%= sourceRepositoryName %>}) protected todoListRepo: <%= sourceRepositoryName %>, - ) {} + @repository(<%= sourceRepositoryClassName %>) protected <%= paramSourceRepository %>: <%= sourceRepositoryClassName %>, + ) { } - @post('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + @get('/<%= sourceModelName %>s/{id}/<%= targetModelName %>s', { responses: { '200': { - description: '<%= sourceClassName %>.<%= targetClassName %> model instance', - content: {'application/json': {schema: {'x-ts-type': Todo}}}, + description: "Array of <%= targetModelClassName %>'s belonging to <%= sourceModelClassName %>", + content: { + 'application/json': { + schema: { type: 'array', items: { 'x-ts-type': <%= targetModelClassName %> } }, + }, + }, }, }, }) - async create( + async find( @param.path.number('id') id: number, - @requestBody() todo: Todo, - ): Promise { - return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).create(todo); + @param.query.object('filter') filter?: Filter, + ): Promise<<%= targetModelClassName %>[]> { + return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).find(filter); } - @get('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + @post('/<%= sourceModelName %>s/{id}/<%= targetModelName %>s', { responses: { '200': { - description: "Array of Todo's belonging to TodoList", - content: { - 'application/json': { - schema: {type: 'array', items: {'x-ts-type': Todo}}, - }, - }, + description: '<%= sourceModelClassName %> model instance', + content: { 'application/json': { schema: { 'x-ts-type': <%= targetModelClassName %> } } }, }, }, }) - async find( - @param.path.number('id') id: number, - @param.query.object('filter') filter?: Filter, - ): Promise { - return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).find(filter); + async create( + @param.path.number('id') poolId: typeof <%= sourceModelClassName %>.prototype.id, + @requestBody() <%= targetModelName %>: <%= targetModelClassName %>, + ): Promise<<%= targetModelClassName %>> { + return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(poolId).create(<%= targetModelName %>); } - @patch('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + @patch('/<%= sourceModelName %>s/{id}/<%= targetModelName %>s', { responses: { '200': { - description: '<%= sourceClassName %>.<%= targetClassName %> PATCH success count', - content: {'application/json': {schema: CountSchema}}, + description: '<%= sourceModelClassName %>.<%= targetModelClassName %> PATCH success count', + content: { 'application/json': { schema: CountSchema } }, }, }, }) async patch( @param.path.number('id') id: number, - @requestBody() todo: Partial, - @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, + @requestBody() <%= targetModelName %>: Partial<<%= targetModelClassName %>>, + @param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where, ): Promise { - return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).patch(todo, where); + return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).patch(<%= targetModelName %>, where); } - @del('/<%= sourceClassName %>/{id}/<%= relationPropertyName %>', { + @del('/<%= sourceModelName %>s/{id}/<%= targetModelName %>s', { responses: { '200': { - description: '<%= sourceClassName %>.<%= targetClassName %> DELETE success count', - content: {'application/json': {schema: CountSchema}}, + description: '<%= sourceModelClassName %>.<%= targetModelClassName %> DELETE success count', + content: { 'application/json': { schema: CountSchema } }, }, }, }) async delete( @param.path.number('id') id: number, - @param.query.object('where', getWhereSchemaFor(Todo)) where?: Where, + @param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where, ): Promise { - return await this.<%= targetClassName %>ListRepo.<%= relationPropertyName %>(id).delete(where); + return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).delete(where); } } From cdeb4f4d81498c111c2af67a988b6298df096afe Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 5 Feb 2019 06:45:33 -0500 Subject: [PATCH 21/89] Code cleanup. --- .../generators/relation/controllerRelation.js | 26 ++----------------- ...ntroller-relation-template-has-many.ts.ejs | 4 +-- 2 files changed, 4 insertions(+), 26 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 12d8abeaf91c..31e82674048c 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -8,12 +8,8 @@ const _ = require('lodash'); const ArtifactGenerator = require('../../lib/artifact-generator'); -const debug = require('../../lib/debug')('relation-generator'); -const inspect = require('util').inspect; const path = require('path'); -const chalk = require('chalk'); const utils = require('../../lib/utils'); -const ast = require('ts-simple-ast'); const CONTROLLER_TEMPLATE_PATH_HAS_MANY = 'controller-relation-template-has-many.ts.ejs'; const CONTROLLER_TEMPLATE_PATH_HAS_ONE = 'controller-relation-template-has-one.ts.ejs'; @@ -96,7 +92,6 @@ module.exports = class ControllerRelation extends ArtifactGenerator { return; } - generateControllerRelationHasMany( sourceModel, targetModel, @@ -115,6 +110,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); this.artifactInfo.targetModelName = utils.kebabCase(targetModel); + this.artifactInfo.relationPropertyName = 'fooo'; + this.artifactInfo.foreignKey = foreignKey; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); @@ -137,25 +134,6 @@ module.exports = class ControllerRelation extends ArtifactGenerator { ) { throw new Error('Not implemented'); } - getKeyType(sourceFile, propertyName) { - const classObj = this.getClassObj(sourceFile); - if ( - classObj - .getProperties() - .map(x => x.getName()) - .includes(propertyName) - ) { - return classObj - .getProperty(propertyName) - .getType() - .getText(); - } - } - - getClassObj(fileName) { - const className = fileName.getClasses()[0].getNameOrThrow(); - return fileName.getClassOrThrow(className); - } addFileToProject(project, fileName) { try { diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs index 35954e78d91a..b7a8a5648bb0 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -50,10 +50,10 @@ export class <%= controllerClassName %> { }, }) async create( - @param.path.number('id') poolId: typeof <%= sourceModelClassName %>.prototype.id, + @param.path.number('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, @requestBody() <%= targetModelName %>: <%= targetModelClassName %>, ): Promise<<%= targetModelClassName %>> { - return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(poolId).create(<%= targetModelName %>); + return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(<%= relationPropertyName %>).create(<%= targetModelName %>); } @patch('/<%= sourceModelName %>s/{id}/<%= targetModelName %>s', { From d7ec612dd91493abb641baa7163778fa3f74504a Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 5 Feb 2019 06:52:54 -0500 Subject: [PATCH 22/89] Updated params to create relation controller. --- packages/cli/generators/relation/controllerRelation.js | 8 ++++---- packages/cli/generators/relation/index.js | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 31e82674048c..d56993ef5be5 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -13,7 +13,7 @@ const utils = require('../../lib/utils'); const CONTROLLER_TEMPLATE_PATH_HAS_MANY = 'controller-relation-template-has-many.ts.ejs'; const CONTROLLER_TEMPLATE_PATH_HAS_ONE = 'controller-relation-template-has-one.ts.ejs'; -const CONTROLLER_TEMPLATE_PATH_BELONGSTO = 'controller-relation-template-belongsto.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_BELONGS_TO = 'controller-relation-template-belongsto.ts.ejs'; // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { @@ -77,9 +77,9 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); this.artifactInfo.targetModelName = utils.kebabCase(targetModel); - this.artifactInfo.relationPropertyName = 'fooo'; + this.artifactInfo.relationPropertyName = relationName; - const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGSTO); + const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); this.artifactInfo.name = sourceModel + '-' + targetModel; this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; @@ -110,7 +110,7 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); this.artifactInfo.targetModelName = utils.kebabCase(targetModel); - this.artifactInfo.relationPropertyName = 'fooo'; + this.artifactInfo.relationPropertyName = relationName; this.artifactInfo.foreignKey = foreignKey; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 4592ed802bff..5ad0cfeb5b46 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -81,6 +81,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { debug('Invoke Controller generator...'); + this.options.relationName = 'bzzzzzzzzzzzzzzz'; + let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; @@ -91,7 +93,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.options.sourceModel, this.options.destinationModel, this.options.foreignKey, - this.options.relationType, + this.options.relationName, ); break; case RELATION_TYPE_HAS_MANY: @@ -99,7 +101,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.options.sourceModel, this.options.destinationModel, this.options.foreignKey, - this.options.relationType, + this.options.relationName, ); break; case RELATION_TYPE_HAS_ONE: @@ -107,7 +109,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.options.sourceModel, this.options.destinationModel, this.options.foreignKey, - this.options.relationType, + this.options.relationName, ); break; default: From a377521cb912265499a356f33f58c9491abbe0e9 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Wed, 6 Feb 2019 03:11:37 -0500 Subject: [PATCH 23/89] Updated script to add relation to model. --- packages/cli/generators/relation/index.js | 23 +- .../cli/generators/relation/modelRelation.js | 446 ++++++++---------- 2 files changed, 202 insertions(+), 267 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 5ad0cfeb5b46..20fc47d1b273 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -116,19 +116,18 @@ module.exports = class RelationGenerator extends ArtifactGenerator { throw new Error('Incorrect Relation Type'); } + //Invoke here Model and Repository Generators + debug('Invoke Model generator...'); + let model = new ModelRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathModel; + model.generateRelationModel( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); /* - //Invoke here Model and Repository Generators - debug('Invoke Model generator...'); - let model = new ModelRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathModel; - model.generateRelationModel( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); - debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 5ade0f456c6b..246eb50d165c 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -1,14 +1,13 @@ +"use strict"; const ArtifactGenerator = require('../../lib/artifact-generator'); - -const ast = require('ts-simple-ast'); +const debug = require('../../lib/debug')('relation-generator'); +const inspect = require('util').inspect; const path = require('path'); +const chalk = require('chalk'); const utils = require('../../lib/utils'); -const relationUtils = require('./relationutils'); +const ast = require('ts-simple-ast'); -module.exports = class RepositoryRelation extends ArtifactGenerator { - constructor(args, opts) { - super(args, opts); - } +module.exports = class ModelRelation extends ArtifactGenerator { _setupGenerator() { super._setupGenerator(); @@ -16,316 +15,253 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { type: 'relation', rootDir: utils.sourceRootDir, }; - this.artifactInfo.repositoriesDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories', - ); - this.artifactInfo.modelsDir = path.resolve( + + this.artifactInfo.modelDir = path.resolve( this.artifactInfo.rootDir, 'models', ); } - generateRelationRepository( - sourceModel, - targetModel, - foreignKey, - relationName, - ) { - this.initializeProperties(sourceModel, targetModel, relationName); - this.handleImports(); - this.handleProperties(); - this.handleConstructor(); - this.artifactInfo.srcRepositoryFile.save(); + + generateRelationModel(sourceModel, targetModel, foreignKey, relationName) { + let modelPath = this.artifactInfo.modelDir; + if ((relationName == "hasMany") || (relationName == "hasOne")) { + this.generateModel(sourceModel, targetModel, relationName, modelPath, foreignKey); + this.generateModel(targetModel, sourceModel, "belongsTo", modelPath); + } + else { + this.generateModel(sourceModel, targetModel, relationName, modelPath, foreignKey); + } } - initializeProperties(sourceModel, targetModel, relationName) { - this.artifactInfo.srcModelFile = path.resolve( - this.artifactInfo.modelsDir, - sourceModel + '.model.ts', - ); + addFileToProject(project, path, modelName) { + const fileName = path + "/" + modelName + '.model.ts'; + return project.addExistingSourceFile(fileName); + } - this.artifactInfo.dstModelFile = path.resolve( - this.artifactInfo.modelsDir, - targetModel + '.model.ts', - ); + getClassesCount(fileName) { + return fileName.getClasses().length; + } - this.artifactInfo.srcModelClass = this.getClassName( - this.artifactInfo.srcModelFile, - ); - this.artifactInfo.dstModelClass = this.getClassName( - this.artifactInfo.dstModelFile, - ); - this.artifactInfo.srcRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - sourceModel + '.repository.ts', - ); + getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } - this.artifactInfo.dstRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - targetModel + '.repository.ts', - ); - this.artifactInfo.srcRepositoryClassName = this.getClassName( - this.artifactInfo.srcRepositoryFile, - ); + isClassExist(fileName) { + return (this.getClassesCount(fileName) == 1); + } - this.artifactInfo.dstRepositoryClassName = this.getClassName( - this.artifactInfo.dstRepositoryFile, - ); + getPropertiesCount(classObj) { + return classObj.getProperties().length; + } - this.artifactInfo.relationName = relationName; + getPropertyStartPos(classObj) { + return classObj.getChildSyntaxList().getChildAtIndex(this.getPropertiesCount(classObj) - 1).getPos() + } - this.artifactInfo.relationProperty = { - name: this.getRelationPropertyName(), - type: this.getRelationPropertyType(), - }; + getClassProperties(classObj) { + return classObj.getProperties() + } - this.artifactInfo.srcRepositoryFile = new ast.Project().addExistingSourceFile( - this.artifactInfo.srcRepositoryFile, - ); + isPropertyExist(classObj, propertyName) { + return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) } - getRelationPropertyName() { - let propertyName = this.artifactInfo.dstModelClass[0].toLowerCase(); - propertyName += this.artifactInfo.dstModelClass.substring(1); + generatePorperty(fileName) { + let name = fileName.getClasses()[0].getNameOrThrow() + return name.charAt(0).toLowerCase() + name.slice(1) + } - if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyName += 's'; + getKey(classObj) { + for (let i = 0; i < this.getPropertiesCount(classObj); i++) { + if (classObj.getProperties()[i].getDecorators()[0].getName() == 'property') { + if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperties().map(x => x.getName()).includes('id')) { + if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperty('id').getInitializer().getText() == 'true') { + return (classObj.getProperties()[i].getName()) + } + } + } } - return propertyName; + throw new Error(' primary Key is missing ') } - getRelationPropertyType() { - let propertyType = this.capitalizeFirstLetter( - this.artifactInfo.relationName, - ); - if ( - this.artifactInfo.relationName == relationUtils.relationType.belongsTo - ) { - propertyType += 'Accessor'; - } else if ( - this.artifactInfo.relationName == relationType.hasOne || - this.artifactInfo.relationName == relationUtils.relationType.hasMany - ) { - propertyType += 'RepositoryFactory'; + getForeignKey(classObj, foreignKey) { + if (foreignKey === undefined) { + return this.getKey(classObj) } else { - throw Error('relation is invalid'); + if (foreignKey == this.getKey(classObj)) { + let name = classObj.getName() + return (name.charAt(0).toLowerCase() + name.slice(1) + foreignKey.charAt(0).toUpperCase() + foreignKey.slice(1)) + } } - propertyType = - propertyType + - '<' + - this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + - ', typeof ' + - this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + - '.prototype.id>'; - - return propertyType; + return (foreignKey) } - getClassName(fileName) { - let sourceFile = new ast.Project().addExistingSourceFile(fileName); - let className = sourceFile.getClasses()[0].getNameOrThrow(); - return className; + getType(classObj, propertyName) { + return classObj.getProperty(propertyName).getType().getText() } + generateModel(sourceModel, targetModel, relationName, path, foreignKey) { - handleImports() { - let requierdImports = this.getRequiredImports(); - this.addRequiredImports(requierdImports); - } + let project = new ast.Project(); - getRequiredImports() { - let importsArray = [ - { - name: this.artifactInfo.dstModelClass, - module: '../models', - }, - { - name: 'repository', - module: '@loopback/repository', - }, - { - name: 'Getter', - module: '@loopback/core', - }, - { - name: this.artifactInfo.dstRepositoryClassName, - module: './index', - }, - ]; + const sourceFile = this.addFileToProject(project, path, utils.kebabCase(sourceModel)); + if (!this.isClassExist(sourceFile)) { + return; + } + const sourceClass = this.getClassObj(sourceFile); - let RelationName = this.capitalizeFirstLetter( - this.artifactInfo.relationName, - ); - switch (this.artifactInfo.relationName) { - case relationType.hasMany: - importsArray.push({ - name: RelationName + 'RepositoryFactory', - module: '@loopback/repository', - }); + const targetFile = this.addFileToProject(project, path, utils.kebabCase(targetModel)); + if (!this.isClassExist(targetFile)) { + return; + } + const targetClass = this.getClassObj(targetFile); + let modelProperty; + + switch (relationName) { + case "hasMany": + if (this.isPropertyExist((sourceClass), (this.generatePorperty(targetFile) + 's'))) { + // TODO add error to CLI UI + console.log('property ' + this.generatePorperty(targetFile) + 's exsist in the model') + throw new Error(' Property exsists') + } + if (this.isPropertyExist((targetClass), this.getForeignKey(sourceClass, foreignKey))) { + // TODO add error to CLI UI + console.log('worng property ' + this.getForeignKey(sourceClass, foreignKey) + ' in the target model ') + throw new Error(' FK is Missing') + } + else { + modelProperty = this.getHasMany(targetClass.getName(), this.getForeignKey(sourceClass, foreignKey)); + + } break; - case relationType.hasOne: - importsArray.push({ - name: RelationName + 'RepositoryFactory', - module: '@loopback/repository', - }); + case "hasOne": + if (this.isPropertyExist((sourceClass), (this.generatePorperty(targetFile)))) { + // TODO add error to CLI UI + console.log('property ' + generatePorperty(targetFile) + ' exsist in the model') + throw new Error(' Property exsists') + } + if (this.isPropertyExist((targetClass), this.getForeignKey(sourceClass, foreignKey))) { + // TODO add error to CLI UI + console.log('worng property ' + this.getForeignKey(sourceClass, foreignKey) + ' in the target model ') + throw new Error(' FK is Missing') + } + else { + modelProperty = this.getHasOne(targetClass.getName()); + } break; - - case relationType.belongsTo: - importsArray.push({ - name: RelationName + 'Accessor', - module: '@loopback/repository', - }); + case "belongsTo": + + if (this.isPropertyExist((sourceClass), (this.generatePorperty(targetFile) + 'Id'))) { + // TODO add error to CLI UI + console.log('property ' + this.generatePorperty(targetFile) + 'Id exsist in the model') + throw new Error(' Property exsists') + } + if (!(this.isPropertyExist((targetClass), this.getForeignKey(sourceClass, foreignKey)))) { + // TODO add error to CLI UI + console.log('worng property ' + this.getForeignKey(sourceClass, foreignKey) + ' in the target model ') + throw new Error(' FK is Missing') + } + else { + modelProperty = this.getBelongsTo(targetClass.getName(), this.getForeignKey(sourceClass, foreignKey), this.getType(targetClass, this.getForeignKey(sourceClass, foreignKey))); + } break; - - default: - result = false; } - - return importsArray; + sourceClass.insertProperty(this.getPropertiesCount(sourceClass), modelProperty); + sourceClass.insertText(this.getPropertyStartPos(sourceClass), "\n") + this.addRequiredImports(sourceFile, targetModel, relationName, targetClass.getName()); + sourceClass.formatText() + sourceFile.save(); } - addRequiredImports(requiredImports) { - for (let currentImport of requiredImports) { - this.addImport(currentImport, this.artifactInfo.srcRepositoryFile); - } - } - addImport(requiredImport) { - if (!this.doesModuleExist(requiredImport)) { - this.addImportWithNonExistingModule(requiredImport); - } else { - this.addImportsWithExistingModule(requiredImport); + getHasMany(className, fk) { + let relationProperty = { + decorators: [{ name: "hasMany", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], + name: className.toLocaleLowerCase() + "s", + type: className + "[]", } - } - addImportWithNonExistingModule(requiredImport) { - this.artifactInfo.srcRepositoryFile.addImportDeclaration({ - moduleSpecifier: requiredImport.module, - namedImports: [requiredImport.name], - }); + return (relationProperty) } - addImportsWithExistingModule(requiredImport) { - let moduleName = requiredImport.module; - let importDeclcaration = this.artifactInfo.srcRepositoryFile.getImportDeclarationOrThrow( - moduleName, - ); - if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { - importDeclcaration.addNamedImport(requiredImport.name); + getHasOne(className) { + let relationProperty = { + decorators: [{ name: "hasOne", arguments: ['() => ' + className] }], + name: className.toLocaleLowerCase(), + type: className, } + + return (relationProperty) } - doesImportExist(importDelcaration, importName) { - let allNamedImports = importDelcaration.getNamedImports(); - for (let currentNamedImport of allNamedImports) { - if (currentNamedImport.getName() == importName) { - return true; - } + getBelongsTo(className, fk, fktype) { + let relationProperty + relationProperty = { + decorators: [{ name: "belongsTo", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], + name: className.toLocaleLowerCase() + 'Id', + type: fktype, } - return false; + return (relationProperty) } - doesModuleExist(importDeclaration) { - let moduleName = importDeclaration.module; - let relevantImport = this.artifactInfo.srcRepositoryFile.getImportDeclaration( - moduleName, - ); - return relevantImport != undefined; + + addRequiredImports(sourceFile, targetModel, relationName, targetClassName) { + let importsArray = this.getRequiredImports(targetModel, relationName, targetClassName); + while (importsArray.length > 0) { + let currentImport = importsArray.pop(); + this.addCurrentImport(sourceFile, currentImport); + } } - handleProperties() { - let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( - this.artifactInfo.srcRepositoryClassName, - ); - this.addProperty(classDeclaration); + getRequiredImports(targetModel, relationName, targetClassName) { - this.orderProperties(classDeclaration); - } + let importsArray = [{ + name: targetClassName, + module: "./" + targetModel + ".model" + }, { + name: relationName, + module: "@loopback/repository" + }, + ]; - addProperty(classDeclaration) { - classDeclaration.addProperty({ - scope: ast.Scope.Public, - isReadonly: true, - name: this.artifactInfo.relationProperty.name, - type: this.artifactInfo.relationProperty.type, - }); + return importsArray; } - orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function(currentProperty) { - currentProperty.setOrder(0); - }); + addCurrentImport(sourceFile, currentImport) { + if (!this.doesModuleExists(sourceFile, currentImport.module)) { + sourceFile.addImportDeclaration({ + moduleSpecifier: currentImport.module + }); + } + if (!this.doesImportExistInModule(sourceFile, currentImport)) { + sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); + } } - handleConstructor() { - let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( - this.artifactInfo.srcRepositoryClassName, - ); - let classConstructor = classDeclaration.getConstructors()[0]; - - this.addParameters(classConstructor); - - this.addCreator(classConstructor); - } - addParameters(classConstructor) { - classConstructor.addParameter({ - decorators: [ - { - name: 'repository.getter', - arguments: ["'" + this.artifactInfo.dstRepositoryClassName + "'"], - }, - ], - name: - this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - 'Getter', - type: 'Getter<' + this.artifactInfo.dstRepositoryClassName + '>,', - scope: ast.Scope.Protected, - }); + doesModuleExists(sourceFile, moduleName) { + return sourceFile.getImportDeclaration(moduleName); } - addCreator(classConstructor) { - let statement = - 'this.create' + - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationType.belongsTo) { - statement += 'Accessor'; - } else if ( - this.artifactInfo.relationName == relationType.hasMany || - this.artifactInfo.relationName == relationType.hasOne - ) { - statement += 'RepositoryFactory'; - } else { - throw Error('relation is invalid'); + doesImportExistInModule(sourceFile, currentImport) { + let identicalImport; + let relevantImports = this.getNamedImportsFromModule(sourceFile, currentImport.module); + if (relevantImports.length > 0) { + identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); } - statement += 'For('; - - let parameter1 = "'" + this.artifactInfo.relationProperty.name + "',"; - let paramater2 = - this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - 'Getter,'; - - statement = - 'this.' + - this.artifactInfo.relationProperty.name + - '=' + - statement + - parameter1 + - paramater2 + - ');'; - - classConstructor.insertStatements(1, statement); - } - capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); + return (identicalImport && identicalImport.length > 0); } - regularizeFirstLetter(string) { - return string.charAt(0).toLowerCase() + string.slice(1); + getNamedImportsFromModule(sourceFile, moduleName) { + let allImports = sourceFile.getImportDeclarations(); + let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); + return relevantImports; } -}; + +} From 549d13a8d886a490bb27c0dc634255f8c1bda4ee Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Wed, 6 Feb 2019 04:09:15 -0500 Subject: [PATCH 24/89] Added prompt relation name. --- packages/cli/generators/relation/index.js | 60 +++++++++++++++-------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 20fc47d1b273..36816ce65fad 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -20,6 +20,7 @@ const ModelRelation = require('./modelRelation'); const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE__SOURCE_MODEL = 'Please select source model'; const PROMPT_MESSAGE__TARGET__MODEL = 'Please select target model'; +const PROMPT_MESSAGE__PROPERTY_NAME = 'Property name for the relation'; const RELATION_TYPE_BELONGS_TO = 'belongsTo'; const RELATION_TYPE_HAS_MANY = 'hasMany'; @@ -81,8 +82,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { debug('Invoke Controller generator...'); - this.options.relationName = 'bzzzzzzzzzzzzzzz'; - let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; @@ -115,30 +114,30 @@ module.exports = class RelationGenerator extends ArtifactGenerator { default: throw new Error('Incorrect Relation Type'); } - - //Invoke here Model and Repository Generators - debug('Invoke Model generator...'); - let model = new ModelRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathModel; - model.generateRelationModel( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); /* - debug('Invoke Repository generator...'); - let repo = new RepositoryRelation(this.args, this.opts); + //Invoke here Model and Repository Generators + debug('Invoke Model generator...'); + let model = new ModelRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathRepo; - repo.generateRelationRepository( + this.artifactInfo.relPath = relPathModel; + model.generateRelationModel( this.options.sourceModel, this.options.destinationModel, this.options.foreignKey, this.options.relationType, ); - */ + + debug('Invoke Repository generator...'); + let repo = new RepositoryRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathRepo; + repo.generateRelationRepository( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + */ return; } @@ -284,9 +283,28 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } } this.options.foreignKey = idProperty; - //Generate this repository - await this._scaffold(); + //} } } + + async promptRelationName() { + if (this.shouldExit()) return false; + + const defaultRelationName = utils.pluralize(utils.camelCase(this.options.destinationModel)); + + this.artifactInfo.relationName = await this.prompt([ + { + type: 'string', + name: 'value', + message: PROMPT_MESSAGE__PROPERTY_NAME, + default: defaultRelationName, + when: !this.artifactInfo.relationName, + }, + ]); + this.options.relationName = this.artifactInfo.relationName.value; + + //Generate this repository + await this._scaffold(); + } }; From aee980e794471f98c89aae214ae03ce7481f0e45 Mon Sep 17 00:00:00 2001 From: Or Shlomo Date: Wed, 6 Feb 2019 12:37:43 +0200 Subject: [PATCH 25/89] docs(docs): updated "lb4 relation" cli command documentation --- docs/site/Relation-generator.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/site/Relation-generator.md b/docs/site/Relation-generator.md index 46f8801bed02..359c7b354cde 100644 --- a/docs/site/Relation-generator.md +++ b/docs/site/Relation-generator.md @@ -8,8 +8,7 @@ permalink: /doc/en/lb4/Relation-generator.html {% include content/generator-create-app.html lang=page.lang %} -The models involved in the relation must also exist before running this -generator. +The models involved in the relation must exist before running this generator. ### Synopsis @@ -26,9 +25,9 @@ TBD ### Arguments -`` - Name of the ID property in the source model to create as an -argument to the command. If provided, the tool will use that as the default when -it prompts for the ID property name. +`` - Name of the ID property that will be created in the source +model. If not provided, the tool will use `id` as the default name when it +prompts for the ID property name. ### Interactive Prompts @@ -36,7 +35,7 @@ The tool will prompt you for: - **Relation `type` between models.** _(relationBaseClass)_ Prompts a list of available relations to choose from as the type of the relation between the - source model and the target model. Relation types supported: + source model and the target model. Supported relation types: - hasMany - hasOne @@ -46,12 +45,12 @@ The tool will prompt you for: models to choose from as the source model of the relation. - **Name of the `target` model.** _(targetModel)_ Prompts a list of available - models to choose from as the target model of the relation. Please note - this - will not let you choose the same model as the source model chosen. + models to choose from as the target model of the relation. Note: The selected + `source` model will not appear in the `target` model list. - **Name of the `ID property` in the source model.** _(Optional, default: `id`)_ Prompts for the ID property name (serves as the foreign key) in the source - model. Leave blank for using default. + model. Note: Leave blank to use the default. - **Name for the `property relation` in the source model.** TBD From 89ea3bbd469fe3fde1bce409d101c7183415ca7b Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Wed, 6 Feb 2019 14:36:11 +0200 Subject: [PATCH 26/89] feat(cli): fix update hasMany and belongsto Models --- packages/cli/generators/relation/index.js | 103 +++++++++--------- .../cli/generators/relation/modelRelation.js | 98 ++++++----------- 2 files changed, 83 insertions(+), 118 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 36816ce65fad..6c8af9c5155e 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -79,65 +79,66 @@ module.exports = class RelationGenerator extends ArtifactGenerator { "'sourceModel' and 'destinationModel' parameter values should be different.", ); } - - debug('Invoke Controller generator...'); - - let ctrl = new ControllerRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathCtrl; - - switch (this.options.relationType) { - case RELATION_TYPE_BELONGS_TO: - ctrl.generateControllerRelationBelongsTo( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationName, - ); - break; - case RELATION_TYPE_HAS_MANY: - ctrl.generateControllerRelationHasMany( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationName, - ); - break; - case RELATION_TYPE_HAS_ONE: - ctrl.generateControllerRelationHasOne( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationName, - ); - break; - default: - throw new Error('Incorrect Relation Type'); - } /* - //Invoke here Model and Repository Generators - debug('Invoke Model generator...'); - let model = new ModelRelation(this.args, this.opts); + debug('Invoke Controller generator...'); + + let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathModel; - model.generateRelationModel( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); + this.artifactInfo.relPath = relPathCtrl; - debug('Invoke Repository generator...'); - let repo = new RepositoryRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathRepo; - repo.generateRelationRepository( + switch (this.options.relationType) { + case RELATION_TYPE_BELONGS_TO: + ctrl.generateControllerRelationBelongsTo( this.options.sourceModel, this.options.destinationModel, this.options.foreignKey, - this.options.relationType, + this.options.relationName, ); + break; + case RELATION_TYPE_HAS_MANY: + ctrl.generateControllerRelationHasMany( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationName, + ); + break; + case RELATION_TYPE_HAS_ONE: + ctrl.generateControllerRelationHasOne( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationName, + ); + break; + default: + throw new Error('Incorrect Relation Type'); + } */ + //Invoke here Model and Repository Generators + debug('Invoke Model generator...'); + let model = new ModelRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathModel; + model.generateRelationModel( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + this.options.relationName, + ); + /* + debug('Invoke Repository generator...'); + let repo = new RepositoryRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathRepo; + repo.generateRelationRepository( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + */ return; } diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 246eb50d165c..6a28127163b3 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -23,15 +23,9 @@ module.exports = class ModelRelation extends ArtifactGenerator { } - generateRelationModel(sourceModel, targetModel, foreignKey, relationName) { + generateRelationModel(sourceModel, targetModel, foreignKey, relationType, relationName) { let modelPath = this.artifactInfo.modelDir; - if ((relationName == "hasMany") || (relationName == "hasOne")) { - this.generateModel(sourceModel, targetModel, relationName, modelPath, foreignKey); - this.generateModel(targetModel, sourceModel, "belongsTo", modelPath); - } - else { - this.generateModel(sourceModel, targetModel, relationName, modelPath, foreignKey); - } + this.generateModel(sourceModel, targetModel, relationType, modelPath, foreignKey, relationName); } addFileToProject(project, path, modelName) { @@ -45,9 +39,8 @@ module.exports = class ModelRelation extends ArtifactGenerator { - getClassObj(fileName) { - const className = fileName.getClasses()[0].getNameOrThrow(); - return fileName.getClassOrThrow(className); + getClassObj(fileName, modelName) { + return fileName.getClassOrThrow(modelName); } @@ -71,10 +64,6 @@ module.exports = class ModelRelation extends ArtifactGenerator { return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) } - generatePorperty(fileName) { - let name = fileName.getClasses()[0].getNameOrThrow() - return name.charAt(0).toLowerCase() + name.slice(1) - } getKey(classObj) { for (let i = 0; i < this.getPropertiesCount(classObj); i++) { @@ -89,22 +78,11 @@ module.exports = class ModelRelation extends ArtifactGenerator { throw new Error(' primary Key is missing ') } - getForeignKey(classObj, foreignKey) { - if (foreignKey === undefined) { - return this.getKey(classObj) - } else { - if (foreignKey == this.getKey(classObj)) { - let name = classObj.getName() - return (name.charAt(0).toLowerCase() + name.slice(1) + foreignKey.charAt(0).toUpperCase() + foreignKey.slice(1)) - } - } - return (foreignKey) - } getType(classObj, propertyName) { return classObj.getProperty(propertyName).getType().getText() } - generateModel(sourceModel, targetModel, relationName, path, foreignKey) { + generateModel(sourceModel, targetModel, relationType, path, foreignKey, relationName) { let project = new ast.Project(); @@ -112,76 +90,62 @@ module.exports = class ModelRelation extends ArtifactGenerator { if (!this.isClassExist(sourceFile)) { return; } - const sourceClass = this.getClassObj(sourceFile); + const sourceClass = this.getClassObj(sourceFile, sourceModel); const targetFile = this.addFileToProject(project, path, utils.kebabCase(targetModel)); if (!this.isClassExist(targetFile)) { return; } - const targetClass = this.getClassObj(targetFile); + const targetClass = this.getClassObj(targetFile, targetModel); let modelProperty; - switch (relationName) { + switch (relationType) { case "hasMany": - if (this.isPropertyExist((sourceClass), (this.generatePorperty(targetFile) + 's'))) { - // TODO add error to CLI UI - console.log('property ' + this.generatePorperty(targetFile) + 's exsist in the model') + if (this.isPropertyExist((sourceClass), relationName)) { + console.log('property ' + relationName + ' exsist in the model') throw new Error(' Property exsists') } - if (this.isPropertyExist((targetClass), this.getForeignKey(sourceClass, foreignKey))) { - // TODO add error to CLI UI - console.log('worng property ' + this.getForeignKey(sourceClass, foreignKey) + ' in the target model ') - throw new Error(' FK is Missing') - } else { - modelProperty = this.getHasMany(targetClass.getName(), this.getForeignKey(sourceClass, foreignKey)); + modelProperty = this.getHasMany(targetModel, relationName); } break; case "hasOne": - if (this.isPropertyExist((sourceClass), (this.generatePorperty(targetFile)))) { - // TODO add error to CLI UI - console.log('property ' + generatePorperty(targetFile) + ' exsist in the model') + if (this.isPropertyExist((sourceClass), relationName)) { + console.log('property ' + relationName + ' exsist in the model') throw new Error(' Property exsists') } - if (this.isPropertyExist((targetClass), this.getForeignKey(sourceClass, foreignKey))) { - // TODO add error to CLI UI - console.log('worng property ' + this.getForeignKey(sourceClass, foreignKey) + ' in the target model ') + if (this.isPropertyExist((targetClass), foreignKey)) { + console.log('worng property ' + foreignKey + ' in the target model ') throw new Error(' FK is Missing') } else { - modelProperty = this.getHasOne(targetClass.getName()); + modelProperty = this.getHasOne(targetModel); } break; case "belongsTo": - - if (this.isPropertyExist((sourceClass), (this.generatePorperty(targetFile) + 'Id'))) { - // TODO add error to CLI UI - console.log('property ' + this.generatePorperty(targetFile) + 'Id exsist in the model') + //fix remvove ID + if (this.isPropertyExist((sourceClass), (relationName + 'Id'))) { + console.log('property ' + relationName + 'Id exsist in the model') throw new Error(' Property exsists') } - if (!(this.isPropertyExist((targetClass), this.getForeignKey(sourceClass, foreignKey)))) { - // TODO add error to CLI UI - console.log('worng property ' + this.getForeignKey(sourceClass, foreignKey) + ' in the target model ') - throw new Error(' FK is Missing') - } else { - modelProperty = this.getBelongsTo(targetClass.getName(), this.getForeignKey(sourceClass, foreignKey), this.getType(targetClass, this.getForeignKey(sourceClass, foreignKey))); + modelProperty = this.getBelongsTo(targetModel, relationName, 'Number'); } break; } sourceClass.insertProperty(this.getPropertiesCount(sourceClass), modelProperty); sourceClass.insertText(this.getPropertyStartPos(sourceClass), "\n") - this.addRequiredImports(sourceFile, targetModel, relationName, targetClass.getName()); + this.addRequiredImports(sourceFile, targetModel, relationType, targetModel); sourceClass.formatText() sourceFile.save(); } - getHasMany(className, fk) { + getHasMany(className, relationName) { let relationProperty = { - decorators: [{ name: "hasMany", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], - name: className.toLocaleLowerCase() + "s", + decorators: [{ name: "hasMany", arguments: ['() => ' + className] }], + name: relationName, type: className + "[]", } @@ -198,19 +162,19 @@ module.exports = class ModelRelation extends ArtifactGenerator { return (relationProperty) } - getBelongsTo(className, fk, fktype) { + getBelongsTo(className, relationName, fktype) { let relationProperty relationProperty = { - decorators: [{ name: "belongsTo", arguments: ['() => ' + className + ", {keyTo: '" + fk + "' }"] }], - name: className.toLocaleLowerCase() + 'Id', + decorators: [{ name: "belongsTo", arguments: ['() => ' + className] }], + name: relationName + 'Id', type: fktype, } return (relationProperty) } - addRequiredImports(sourceFile, targetModel, relationName, targetClassName) { - let importsArray = this.getRequiredImports(targetModel, relationName, targetClassName); + addRequiredImports(sourceFile, targetModel, relationType, targetClassName) { + let importsArray = this.getRequiredImports(targetModel, relationType, targetClassName); while (importsArray.length > 0) { let currentImport = importsArray.pop(); this.addCurrentImport(sourceFile, currentImport); @@ -218,13 +182,13 @@ module.exports = class ModelRelation extends ArtifactGenerator { } - getRequiredImports(targetModel, relationName, targetClassName) { + getRequiredImports(targetModel, relationType, targetClassName) { let importsArray = [{ name: targetClassName, module: "./" + targetModel + ".model" }, { - name: relationName, + name: relationType, module: "@loopback/repository" }, ]; From 670d13e7efd8b1680dfc155dece9dff8b176a2fd Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Wed, 6 Feb 2019 14:51:18 +0200 Subject: [PATCH 27/89] feat(cli): added support for hasOne in model --- .../cli/generators/relation/modelRelation.js | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 6a28127163b3..b593d2f8b458 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -22,7 +22,6 @@ module.exports = class ModelRelation extends ArtifactGenerator { ); } - generateRelationModel(sourceModel, targetModel, foreignKey, relationType, relationName) { let modelPath = this.artifactInfo.modelDir; this.generateModel(sourceModel, targetModel, relationType, modelPath, foreignKey, relationName); @@ -37,13 +36,10 @@ module.exports = class ModelRelation extends ArtifactGenerator { return fileName.getClasses().length; } - - getClassObj(fileName, modelName) { return fileName.getClassOrThrow(modelName); } - isClassExist(fileName) { return (this.getClassesCount(fileName) == 1); } @@ -64,21 +60,6 @@ module.exports = class ModelRelation extends ArtifactGenerator { return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) } - - getKey(classObj) { - for (let i = 0; i < this.getPropertiesCount(classObj); i++) { - if (classObj.getProperties()[i].getDecorators()[0].getName() == 'property') { - if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperties().map(x => x.getName()).includes('id')) { - if (classObj.getProperties()[i].getDecorators()[0].getArguments()[0].getProperty('id').getInitializer().getText() == 'true') { - return (classObj.getProperties()[i].getName()) - } - } - } - } - throw new Error(' primary Key is missing ') - } - - getType(classObj, propertyName) { return classObj.getProperty(propertyName).getType().getText() } @@ -115,12 +96,8 @@ module.exports = class ModelRelation extends ArtifactGenerator { console.log('property ' + relationName + ' exsist in the model') throw new Error(' Property exsists') } - if (this.isPropertyExist((targetClass), foreignKey)) { - console.log('worng property ' + foreignKey + ' in the target model ') - throw new Error(' FK is Missing') - } else { - modelProperty = this.getHasOne(targetModel); + modelProperty = this.getHasOne(targetModel, relationName); } break; case "belongsTo": @@ -152,13 +129,12 @@ module.exports = class ModelRelation extends ArtifactGenerator { return (relationProperty) } - getHasOne(className) { + getHasOne(className, relationName) { let relationProperty = { decorators: [{ name: "hasOne", arguments: ['() => ' + className] }], - name: className.toLocaleLowerCase(), + name: relationName, type: className, } - return (relationProperty) } From b3062e007b42607d43807be68335e9688a0151c3 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Wed, 6 Feb 2019 10:13:46 -0500 Subject: [PATCH 28/89] Fixed default relation name calculation. --- packages/cli/generators/relation/index.js | 25 ++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 6c8af9c5155e..2bb7435331fe 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -292,7 +292,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { async promptRelationName() { if (this.shouldExit()) return false; - const defaultRelationName = utils.pluralize(utils.camelCase(this.options.destinationModel)); + const defaultRelationName = this._getDefaultRelationName(); this.artifactInfo.relationName = await this.prompt([ { @@ -308,4 +308,27 @@ module.exports = class RelationGenerator extends ArtifactGenerator { //Generate this repository await this._scaffold(); } + + _getDefaultRelationName() { + var defaultRelationName; + switch (this.options.relationType) { + case RELATION_TYPE_BELONGS_TO: + defaultRelationName = + utils.camelCase(this.options.destinationModel) + + utils.toClassName(this.options.foreignKey); + break; + case RELATION_TYPE_HAS_MANY: + defaultRelationName = utils.pluralize( + utils.camelCase(this.options.destinationModel) + ); + break; + case RELATION_TYPE_HAS_ONE: + defaultRelationName = utils.camelCase(this.options.destinationModel); + break; + default: + throw new Error('Incorrect Relation Type'); + } + + return defaultRelationName; + } }; From eee94f7d339758d40d68ad749e6e9a6237c87097 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Wed, 6 Feb 2019 10:19:19 -0500 Subject: [PATCH 29/89] Code refactoring. --- packages/cli/generators/relation/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 2bb7435331fe..8461968024b2 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -17,6 +17,8 @@ const ControllerRelation = require('./controllerRelation'); const RepositoryRelation = require('./repositoryRelation'); const ModelRelation = require('./modelRelation'); +const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; + const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE__SOURCE_MODEL = 'Please select source model'; const PROMPT_MESSAGE__TARGET__MODEL = 'Please select target model'; @@ -112,7 +114,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); break; default: - throw new Error('Incorrect Relation Type'); + throw new Error(ERROR_INCORRECT_RELATION_TYPE); } */ //Invoke here Model and Repository Generators @@ -326,7 +328,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { defaultRelationName = utils.camelCase(this.options.destinationModel); break; default: - throw new Error('Incorrect Relation Type'); + throw new Error(ERROR_INCORRECT_RELATION_TYPE); } return defaultRelationName; From a15c9cf12780772f6e01fcc125e31d0b0194c08b Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Wed, 6 Feb 2019 11:00:36 -0500 Subject: [PATCH 30/89] Added foreign key type calculation. --- packages/cli/generators/relation/index.js | 28 ++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 8461968024b2..8eba962b5540 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -12,6 +12,7 @@ const path = require('path'); const chalk = require('chalk'); const utils = require('../../lib/utils'); const tsquery = require('../../lib/ast-helper'); +const ast = require('ts-simple-ast'); const ControllerRelation = require('./controllerRelation'); const RepositoryRelation = require('./repositoryRelation'); @@ -68,6 +69,25 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return tsquery.getIdFromModel(fileContent); } + _getKeyType(sourceFile, propertyName) { + const classObj = this._getClassObj(sourceFile); + if ( + classObj + .getProperties() + .map(x => x.getName()) + .includes(propertyName) + ) { + return classObj + .getProperty(propertyName) + .getType() + .getText(); + } + } + _getClassObj(fileName) { + const className = fileName.getClasses()[0].getNameOrThrow(); + return fileName.getClassOrThrow(className); + } + async _scaffold() { let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; @@ -286,8 +306,14 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } } this.options.foreignKey = idProperty; + let project = new ast.Project(); - //} + const sourceFile = path.join( + this.artifactInfo.modelDir, + utils.getModelFileName(this.artifactInfo.sourceModel.modelNameList), + ); + const sf = project.addExistingSourceFile(sourceFile); + this.options.foreignKeyType = this._getKeyType(sf, this.options.foreignKey); } } From fc9aaaf6a3884cdd28c85e2777528804aba9f3e5 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 05:45:37 -0500 Subject: [PATCH 31/89] Added foreignKeyType to controller. --- .../generators/relation/controllerRelation.js | 48 ++++------- packages/cli/generators/relation/index.js | 85 ++++++++----------- ...ntroller-relation-template-has-many.ts.ejs | 8 +- 3 files changed, 55 insertions(+), 86 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index d56993ef5be5..e956baaad8fd 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -57,14 +57,9 @@ module.exports = class ControllerRelation extends ArtifactGenerator { return; } - generateControllerRelationBelongsTo( - sourceModel, - targetModel, - foreignKey, - relationName, - ) { - this.artifactInfo.sourceModelClassName = sourceModel; - this.artifactInfo.targetModelClassName = targetModel; + generateControllerRelationBelongsTo(options) { + this.artifactInfo.sourceModelClassName = options.sourceModel; + this.artifactInfo.targetModelClassName = options.destinationModel; this.artifactInfo.sourceRepositoryClassName = this.artifactInfo.sourceModelClassName + 'Repository'; this.artifactInfo.controllerClassName = @@ -74,14 +69,14 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceModelClassName + 'Repository', ); - this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); - this.artifactInfo.targetModelName = utils.kebabCase(targetModel); + this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); + this.artifactInfo.targetModelName = utils.kebabCase(options.destinationModel); - this.artifactInfo.relationPropertyName = relationName; + this.artifactInfo.relationPropertyName = options.relationName; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); - this.artifactInfo.name = sourceModel + '-' + targetModel; + this.artifactInfo.name = options.sourceModel + '-' + options.destinationModel; this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; const dest = this.destinationPath( @@ -92,14 +87,9 @@ module.exports = class ControllerRelation extends ArtifactGenerator { return; } - generateControllerRelationHasMany( - sourceModel, - targetModel, - foreignKey, - relationName, - ) { - this.artifactInfo.sourceModelClassName = sourceModel; - this.artifactInfo.targetModelClassName = targetModel; + generateControllerRelationHasMany(options) { + this.artifactInfo.sourceModelClassName = options.sourceModel; + this.artifactInfo.targetModelClassName = options.destinationModel; this.artifactInfo.sourceRepositoryClassName = this.artifactInfo.sourceModelClassName + 'Repository'; this.artifactInfo.controllerClassName = @@ -108,14 +98,15 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceModelClassName + 'Repository', ); - this.artifactInfo.sourceModelName = utils.kebabCase(sourceModel); - this.artifactInfo.targetModelName = utils.kebabCase(targetModel); - this.artifactInfo.relationPropertyName = relationName; - this.artifactInfo.foreignKey = foreignKey; + this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); + this.artifactInfo.targetModelName = utils.kebabCase(options.destinationModel); + this.artifactInfo.relationPropertyName = options.destinationModel; + this.artifactInfo.foreignKey = options.foreignKey; + this.artifactInfo.foreignKeyType = options.foreignKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); - this.artifactInfo.name = sourceModel + '-' + targetModel; + this.artifactInfo.name = options.sourceModel + '-' + options.destinationModel; this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; const dest = this.destinationPath( @@ -126,12 +117,7 @@ module.exports = class ControllerRelation extends ArtifactGenerator { return; } - generateControllerRelationHasOne( - sourceModel, - targetModel, - foreignKey, - relationName, - ) { + generateControllerRelationHasOne(options) { throw new Error('Not implemented'); } diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 8eba962b5540..ab521a5d61ca 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -83,11 +83,23 @@ module.exports = class RelationGenerator extends ArtifactGenerator { .getText(); } } + _getClassObj(fileName) { const className = fileName.getClasses()[0].getNameOrThrow(); return fileName.getClassOrThrow(className); } + _calcForeignKeyType() { + let project = new ast.Project(); + + const sourceFile = path.join( + this.artifactInfo.modelDir, + utils.getModelFileName(this.artifactInfo.sourceModel.modelNameList), + ); + const sf = project.addExistingSourceFile(sourceFile); + this.options.foreignKeyType = this._getKeyType(sf, this.options.foreignKey); + } + async _scaffold() { let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; @@ -101,54 +113,32 @@ module.exports = class RelationGenerator extends ArtifactGenerator { "'sourceModel' and 'destinationModel' parameter values should be different.", ); } - /* - debug('Invoke Controller generator...'); - - let ctrl = new ControllerRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathCtrl; - - switch (this.options.relationType) { - case RELATION_TYPE_BELONGS_TO: - ctrl.generateControllerRelationBelongsTo( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationName, - ); - break; - case RELATION_TYPE_HAS_MANY: - ctrl.generateControllerRelationHasMany( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationName, - ); - break; - case RELATION_TYPE_HAS_ONE: - ctrl.generateControllerRelationHasOne( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationName, - ); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - */ + debug('Invoke Controller generator...'); + + let ctrl = new ControllerRelation(this.args, this.opts); + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathCtrl; + + switch (this.options.relationType) { + case RELATION_TYPE_BELONGS_TO: + ctrl.generateControllerRelationBelongsTo(this.options); + break; + case RELATION_TYPE_HAS_MANY: + ctrl.generateControllerRelationHasMany(this.options); + break; + case RELATION_TYPE_HAS_ONE: + ctrl.generateControllerRelationHasOne(this.options); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathModel; - model.generateRelationModel( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - this.options.relationName, - ); + //model.generateRelationModel(this.options); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); @@ -306,14 +296,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } } this.options.foreignKey = idProperty; - let project = new ast.Project(); - - const sourceFile = path.join( - this.artifactInfo.modelDir, - utils.getModelFileName(this.artifactInfo.sourceModel.modelNameList), - ); - const sf = project.addExistingSourceFile(sourceFile); - this.options.foreignKeyType = this._getKeyType(sf, this.options.foreignKey); + this._calcForeignKeyType(); } } diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs index b7a8a5648bb0..33d3bb292dfb 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -35,7 +35,7 @@ export class <%= controllerClassName %> { }, }) async find( - @param.path.number('id') id: number, + @param.path.<%= foreignKeyType %>('id') id: <%= foreignKeyType %>, @param.query.object('filter') filter?: Filter, ): Promise<<%= targetModelClassName %>[]> { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).find(filter); @@ -50,7 +50,7 @@ export class <%= controllerClassName %> { }, }) async create( - @param.path.number('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, + @param.path.<%= foreignKeyType %>('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, @requestBody() <%= targetModelName %>: <%= targetModelClassName %>, ): Promise<<%= targetModelClassName %>> { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(<%= relationPropertyName %>).create(<%= targetModelName %>); @@ -65,7 +65,7 @@ export class <%= controllerClassName %> { }, }) async patch( - @param.path.number('id') id: number, + @param.path.<%= foreignKeyType %>('id') id: <%= foreignKeyType %>, @requestBody() <%= targetModelName %>: Partial<<%= targetModelClassName %>>, @param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where, ): Promise { @@ -81,7 +81,7 @@ export class <%= controllerClassName %> { }, }) async delete( - @param.path.number('id') id: number, + @param.path.<%= foreignKeyType %>('id') id: <%= foreignKeyType %>, @param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where, ): Promise { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).delete(where); From a914022b7e86de6ca866a6e663081bfd735272a5 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 06:44:43 -0500 Subject: [PATCH 32/89] Updated belnogsto controller. --- packages/cli/generators/relation/controllerRelation.js | 2 ++ .../templates/controller-relation-template-belongsto.ts.ejs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index e956baaad8fd..e1dfe3880559 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -73,6 +73,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.targetModelName = utils.kebabCase(options.destinationModel); this.artifactInfo.relationPropertyName = options.relationName; + this.artifactInfo.foreignKey = options.foreignKey; + this.artifactInfo.foreignKeyType = options.foreignKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); diff --git a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs index 9edc6c0ae66a..a358f9943ce4 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs @@ -16,7 +16,7 @@ export class <%= controllerClassName %> { @get('/<%= sourceModelName %>s/{id}/<%= targetModelName %>') async get<%= targetModelClassName %>( - @param.path.number('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.id, + @param.path.<%= foreignKeyType %>('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, ): Promise<<%= targetModelClassName %>> { return await this.<%= paramSourceRepository %>.<%= targetModelName %>(<%= relationPropertyName %>); } From 4707ef0a2699e122ad7ad6ecb9bdcd06d48e8d9f Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 08:27:53 -0500 Subject: [PATCH 33/89] Running lint:fix. --- .../generators/relation/controllerRelation.js | 37 ++-- packages/cli/generators/relation/index.js | 22 ++- .../cli/generators/relation/modelRelation.js | 186 +++++++++++------- 3 files changed, 155 insertions(+), 90 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index e1dfe3880559..5babd641bc21 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -11,9 +11,12 @@ const ArtifactGenerator = require('../../lib/artifact-generator'); const path = require('path'); const utils = require('../../lib/utils'); -const CONTROLLER_TEMPLATE_PATH_HAS_MANY = 'controller-relation-template-has-many.ts.ejs'; -const CONTROLLER_TEMPLATE_PATH_HAS_ONE = 'controller-relation-template-has-one.ts.ejs'; -const CONTROLLER_TEMPLATE_PATH_BELONGS_TO = 'controller-relation-template-belongsto.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_HAS_MANY = + 'controller-relation-template-has-many.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_HAS_ONE = + 'controller-relation-template-has-one.ts.ejs'; +const CONTROLLER_TEMPLATE_PATH_BELONGS_TO = + 'controller-relation-template-belongsto.ts.ejs'; // Exportable constants module.exports = class ControllerRelation extends ArtifactGenerator { @@ -63,14 +66,18 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceRepositoryClassName = this.artifactInfo.sourceModelClassName + 'Repository'; this.artifactInfo.controllerClassName = - this.artifactInfo.sourceModelClassName + this.artifactInfo.targetModelClassName + 'Controller'; + this.artifactInfo.sourceModelClassName + + this.artifactInfo.targetModelClassName + + 'Controller'; this.artifactInfo.paramSourceRepository = utils.camelCase( this.artifactInfo.sourceModelClassName + 'Repository', ); this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); - this.artifactInfo.targetModelName = utils.kebabCase(options.destinationModel); + this.artifactInfo.targetModelName = utils.kebabCase( + options.destinationModel, + ); this.artifactInfo.relationPropertyName = options.relationName; this.artifactInfo.foreignKey = options.foreignKey; @@ -78,8 +85,10 @@ module.exports = class ControllerRelation extends ArtifactGenerator { const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); - this.artifactInfo.name = options.sourceModel + '-' + options.destinationModel; - this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + this.artifactInfo.name = + options.sourceModel + '-' + options.destinationModel; + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; const dest = this.destinationPath( path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), @@ -95,21 +104,27 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.sourceRepositoryClassName = this.artifactInfo.sourceModelClassName + 'Repository'; this.artifactInfo.controllerClassName = - this.artifactInfo.sourceModelClassName + this.artifactInfo.targetModelClassName + 'Controller'; + this.artifactInfo.sourceModelClassName + + this.artifactInfo.targetModelClassName + + 'Controller'; this.artifactInfo.paramSourceRepository = utils.camelCase( this.artifactInfo.sourceModelClassName + 'Repository', ); this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); - this.artifactInfo.targetModelName = utils.kebabCase(options.destinationModel); + this.artifactInfo.targetModelName = utils.kebabCase( + options.destinationModel, + ); this.artifactInfo.relationPropertyName = options.destinationModel; this.artifactInfo.foreignKey = options.foreignKey; this.artifactInfo.foreignKeyType = options.foreignKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); - this.artifactInfo.name = options.sourceModel + '-' + options.destinationModel; - this.artifactInfo.outFile = utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + this.artifactInfo.name = + options.sourceModel + '-' + options.destinationModel; + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; const dest = this.destinationPath( path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index ab521a5d61ca..150f6c4a4058 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -32,7 +32,7 @@ const RELATION_TYPE_HAS_ONE = 'hasOne'; const availableRelationsBaseClasses = [ RELATION_TYPE_BELONGS_TO, RELATION_TYPE_HAS_MANY, - RELATION_TYPE_HAS_ONE + RELATION_TYPE_HAS_ONE, ]; const relPathControllersFolder = '/controllers'; @@ -195,7 +195,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } if (this.options[parameter]) { - debug(`Model name received from command line: ${this.options[parameter]}`); + debug( + `Model name received from command line: ${this.options[parameter]}`, + ); this.options.model = utils.toClassName(this.options[parameter]); // assign the model name from the command line only if it is valid @@ -204,7 +206,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { modelList.length > 0 && modelList.includes(this.options.model) ) { - Object.assign(this.artifactInfo, { modelNameList: [this.options[parameter]] }); + Object.assign(this.artifactInfo, { + modelNameList: [this.options[parameter]], + }); } else { modelList = []; } @@ -214,8 +218,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -240,7 +244,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return await this._promptModelList( PROMPT_MESSAGE__SOURCE_MODEL, - 'sourceModel' + 'sourceModel', ); } @@ -250,7 +254,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return await this._promptModelList( PROMPT_MESSAGE__TARGET__MODEL, - 'destinationModel' + 'destinationModel', ); } @@ -271,7 +275,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { name: 'propertyName', message: `Please enter the name of the ID property for ${ this.artifactInfo.sourceModel - }:`, + }:`, default: 'id', }, ]; @@ -330,7 +334,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { break; case RELATION_TYPE_HAS_MANY: defaultRelationName = utils.pluralize( - utils.camelCase(this.options.destinationModel) + utils.camelCase(this.options.destinationModel), ); break; case RELATION_TYPE_HAS_ONE: diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index b593d2f8b458..3a2528bc872f 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; const ArtifactGenerator = require('../../lib/artifact-generator'); const debug = require('../../lib/debug')('relation-generator'); const inspect = require('util').inspect; @@ -8,7 +8,6 @@ const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); module.exports = class ModelRelation extends ArtifactGenerator { - _setupGenerator() { super._setupGenerator(); this.artifactInfo = { @@ -22,13 +21,26 @@ module.exports = class ModelRelation extends ArtifactGenerator { ); } - generateRelationModel(sourceModel, targetModel, foreignKey, relationType, relationName) { + generateRelationModel( + sourceModel, + targetModel, + foreignKey, + relationType, + relationName, + ) { let modelPath = this.artifactInfo.modelDir; - this.generateModel(sourceModel, targetModel, relationType, modelPath, foreignKey, relationName); + this.generateModel( + sourceModel, + targetModel, + relationType, + modelPath, + foreignKey, + relationName, + ); } addFileToProject(project, path, modelName) { - const fileName = path + "/" + modelName + '.model.ts'; + const fileName = path + '/' + modelName + '.model.ts'; return project.addExistingSourceFile(fileName); } @@ -41,7 +53,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { } isClassExist(fileName) { - return (this.getClassesCount(fileName) == 1); + return this.getClassesCount(fileName) == 1; } getPropertiesCount(classObj) { @@ -49,31 +61,53 @@ module.exports = class ModelRelation extends ArtifactGenerator { } getPropertyStartPos(classObj) { - return classObj.getChildSyntaxList().getChildAtIndex(this.getPropertiesCount(classObj) - 1).getPos() + return classObj + .getChildSyntaxList() + .getChildAtIndex(this.getPropertiesCount(classObj) - 1) + .getPos(); } getClassProperties(classObj) { - return classObj.getProperties() + return classObj.getProperties(); } isPropertyExist(classObj, propertyName) { - return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) + return this.getClassProperties(classObj) + .map(x => x.getName()) + .includes(propertyName); } getType(classObj, propertyName) { - return classObj.getProperty(propertyName).getType().getText() - } - generateModel(sourceModel, targetModel, relationType, path, foreignKey, relationName) { - + return classObj + .getProperty(propertyName) + .getType() + .getText(); + } + generateModel( + sourceModel, + targetModel, + relationType, + path, + foreignKey, + relationName, + ) { let project = new ast.Project(); - const sourceFile = this.addFileToProject(project, path, utils.kebabCase(sourceModel)); + const sourceFile = this.addFileToProject( + project, + path, + utils.kebabCase(sourceModel), + ); if (!this.isClassExist(sourceFile)) { return; } const sourceClass = this.getClassObj(sourceFile, sourceModel); - const targetFile = this.addFileToProject(project, path, utils.kebabCase(targetModel)); + const targetFile = this.addFileToProject( + project, + path, + utils.kebabCase(targetModel), + ); if (!this.isClassExist(targetFile)) { return; } @@ -81,92 +115,97 @@ module.exports = class ModelRelation extends ArtifactGenerator { let modelProperty; switch (relationType) { - case "hasMany": - if (this.isPropertyExist((sourceClass), relationName)) { - console.log('property ' + relationName + ' exsist in the model') - throw new Error(' Property exsists') - } - else { + case 'hasMany': + if (this.isPropertyExist(sourceClass, relationName)) { + console.log('property ' + relationName + ' exsist in the model'); + throw new Error(' Property exsists'); + } else { modelProperty = this.getHasMany(targetModel, relationName); - } break; - case "hasOne": - if (this.isPropertyExist((sourceClass), relationName)) { - console.log('property ' + relationName + ' exsist in the model') - throw new Error(' Property exsists') - } - else { + case 'hasOne': + if (this.isPropertyExist(sourceClass, relationName)) { + console.log('property ' + relationName + ' exsist in the model'); + throw new Error(' Property exsists'); + } else { modelProperty = this.getHasOne(targetModel, relationName); } break; - case "belongsTo": + case 'belongsTo': //fix remvove ID - if (this.isPropertyExist((sourceClass), (relationName + 'Id'))) { - console.log('property ' + relationName + 'Id exsist in the model') - throw new Error(' Property exsists') - } - else { - modelProperty = this.getBelongsTo(targetModel, relationName, 'Number'); + if (this.isPropertyExist(sourceClass, relationName + 'Id')) { + console.log('property ' + relationName + 'Id exsist in the model'); + throw new Error(' Property exsists'); + } else { + modelProperty = this.getBelongsTo( + targetModel, + relationName, + 'Number', + ); } break; } - sourceClass.insertProperty(this.getPropertiesCount(sourceClass), modelProperty); - sourceClass.insertText(this.getPropertyStartPos(sourceClass), "\n") + sourceClass.insertProperty( + this.getPropertiesCount(sourceClass), + modelProperty, + ); + sourceClass.insertText(this.getPropertyStartPos(sourceClass), '\n'); this.addRequiredImports(sourceFile, targetModel, relationType, targetModel); - sourceClass.formatText() + sourceClass.formatText(); sourceFile.save(); } - getHasMany(className, relationName) { let relationProperty = { - decorators: [{ name: "hasMany", arguments: ['() => ' + className] }], + decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], name: relationName, - type: className + "[]", - } + type: className + '[]', + }; - return (relationProperty) + return relationProperty; } getHasOne(className, relationName) { let relationProperty = { - decorators: [{ name: "hasOne", arguments: ['() => ' + className] }], + decorators: [{name: 'hasOne', arguments: ['() => ' + className]}], name: relationName, type: className, - } - return (relationProperty) + }; + return relationProperty; } getBelongsTo(className, relationName, fktype) { - let relationProperty + let relationProperty; relationProperty = { - decorators: [{ name: "belongsTo", arguments: ['() => ' + className] }], + decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], name: relationName + 'Id', type: fktype, - } - return (relationProperty) + }; + return relationProperty; } - addRequiredImports(sourceFile, targetModel, relationType, targetClassName) { - let importsArray = this.getRequiredImports(targetModel, relationType, targetClassName); + let importsArray = this.getRequiredImports( + targetModel, + relationType, + targetClassName, + ); while (importsArray.length > 0) { let currentImport = importsArray.pop(); this.addCurrentImport(sourceFile, currentImport); } } - getRequiredImports(targetModel, relationType, targetClassName) { - - let importsArray = [{ - name: targetClassName, - module: "./" + targetModel + ".model" - }, { - name: relationType, - module: "@loopback/repository" - }, + let importsArray = [ + { + name: targetClassName, + module: './' + targetModel + '.model', + }, + { + name: relationType, + module: '@loopback/repository', + }, ]; return importsArray; @@ -175,33 +214,40 @@ module.exports = class ModelRelation extends ArtifactGenerator { addCurrentImport(sourceFile, currentImport) { if (!this.doesModuleExists(sourceFile, currentImport.module)) { sourceFile.addImportDeclaration({ - moduleSpecifier: currentImport.module + moduleSpecifier: currentImport.module, }); } if (!this.doesImportExistInModule(sourceFile, currentImport)) { - sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); + sourceFile + .getImportDeclarationOrThrow(currentImport.module) + .addNamedImport(currentImport.name); } } - doesModuleExists(sourceFile, moduleName) { return sourceFile.getImportDeclaration(moduleName); } doesImportExistInModule(sourceFile, currentImport) { let identicalImport; - let relevantImports = this.getNamedImportsFromModule(sourceFile, currentImport.module); + let relevantImports = this.getNamedImportsFromModule( + sourceFile, + currentImport.module, + ); if (relevantImports.length > 0) { - identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); + identicalImport = relevantImports[0] + .getNamedImports() + .filter(imp => imp.getName() == currentImport.name); } - return (identicalImport && identicalImport.length > 0); + return identicalImport && identicalImport.length > 0; } getNamedImportsFromModule(sourceFile, moduleName) { let allImports = sourceFile.getImportDeclarations(); - let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); + let relevantImports = allImports.filter( + imp => imp.getModuleSpecifierValue() == moduleName, + ); return relevantImports; } - -} +}; From 19e31dbe6d877f14d6736d31f777e77b1aa38d81 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 08:29:31 -0500 Subject: [PATCH 34/89] Updated repositoryRelation.js. --- .../generators/relation/repositoryRelation.js | 433 +++++++++--------- 1 file changed, 227 insertions(+), 206 deletions(-) diff --git a/packages/cli/generators/relation/repositoryRelation.js b/packages/cli/generators/relation/repositoryRelation.js index 1f8f94463af2..e7f5000b2b38 100644 --- a/packages/cli/generators/relation/repositoryRelation.js +++ b/packages/cli/generators/relation/repositoryRelation.js @@ -1,117 +1,166 @@ const ArtifactGenerator = require('../../lib/artifact-generator'); -const debug = require('../../lib/debug')('relation-generator'); -const inspect = require('util').inspect; -const path = require('path'); -const chalk = require('chalk'); -const utils = require('../../lib/utils'); -const fs = require('fs'); const ast = require('ts-simple-ast'); +const path = require('path'); +const utils = require('../../lib/utils'); +const relationUtils = require('./relationutils'); -const relationType = { - hasOne: 'hasOne', - hasMany: 'hasMany', - belongsTo: 'belongsTo', -}; module.exports = class RepositoryRelation extends ArtifactGenerator { + + constructor(args, opts) { + super(args, opts); + } + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { type: 'relation', - rootDir: utils.sourceRootDir, + rootDir: utils.sourceRootDir }; - - this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.repositoriesDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories' + ); + this.artifactInfo.modelsDir = path.resolve( this.artifactInfo.rootDir, - 'repositories', + 'models', ); } - generateRelationRepository( - sourceModel, - targetModel, - foreignKey, - relationName, - ) { - let result; - let projectPath = this.artifactInfo.repositoryDir; - - if (!this.doesRepositoryExists(projectPath, sourceModel)) { - throw Error("repository for source model doesn't exist"); - } - if (!doesRepositoryExists(projectPath, targetModel)) { - throw Error("repository for target model doesn't exist"); - } - if ( - relationName != relationType.hasOne && - relationName != relationType.hasMany && - relationName != relationType.belongsTo - ) { - throw Error('relation is invalid'); - } - - generateRelation(projectPath, sourceModel, targetModel, relationName); + generateRelationRepository(sourceModel, targetModel, + foreignKey, relationName) { + this.initializeProperties(sourceModel, targetModel, relationName); + this.handleImports(); + this.handleProperties(); + this.handleConstructor(); + this.artifactInfo.srcRepositoryFile.save(); } - doesRepositoryExists(path, model) { - let tempPath = path + '/src/repositories/' + model + '.repository.ts'; - let result = fs.existsSync(tempPath); - return result; - } + initializeProperties(sourceModel, targetModel, relationName) { - generateRelation(projectPath, sourceModel, targetModel, relationName) { - let sourceFile = initiateSourceFile(projectPath, sourceModel); - addRequiredImports(sourceFile, targetModel, relationName); - addRequiredProperties(sourceFile, sourceModel, targetModel, relationName); - editConstructor(sourceFile, sourceModel, targetModel, relationName); - sourceFile.save(); - } + this.artifactInfo.srcModelFile = path.resolve( + this.artifactInfo.modelsDir, + sourceModel + ".model.ts"); - initiateSourceFile(basePath, modelName) { - let repoPath = createRepositoryPath(basePath, modelName); - let project = new ast.Project(); - let sourceFile = project.addExistingSourceFile(repoPath); + this.artifactInfo.dstModelFile = path.resolve( + this.artifactInfo.modelsDir, + targetModel + ".model.ts"); - return sourceFile; + this.artifactInfo.srcModelClass = + this.getClassName(this.artifactInfo.srcModelFile); + + this.artifactInfo.dstModelClass = + this.getClassName(this.artifactInfo.dstModelFile); + + this.artifactInfo.srcRepositoryFile = path.resolve( + this.artifactInfo.repositoriesDir, + sourceModel + ".repository.ts" + ) + + this.artifactInfo.dstRepositoryFile = path.resolve( + this.artifactInfo.repositoriesDir, + targetModel + ".repository.ts" + ) + + this.artifactInfo.srcRepositoryClassName = + this.getClassName(this.artifactInfo.srcRepositoryFile); + + this.artifactInfo.dstRepositoryClassName = + this.getClassName(this.artifactInfo.dstRepositoryFile); + + this.artifactInfo.relationName = relationName; + + + this.artifactInfo.relationProperty = { + name: this.getRelationPropertyName(), + type: this.getRelationPropertyType() + } + + this.artifactInfo.srcRepositoryFile = new ast.Project(). + addExistingSourceFile(this.artifactInfo.srcRepositoryFile); } - createRepositoryPath(basePath, modelName) { - return basePath + '/src/repositories/' + modelName + '.repository.ts'; + getRelationPropertyName() { + let propertyName = this.artifactInfo.dstModelClass[0].toLowerCase(); + propertyName += this.artifactInfo.dstModelClass.substring(1); + + if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { + propertyName += "s"; + } + return propertyName; } - addRequiredImports(sourceFile, targetModel, relationName) { - let importsArray = getRequiredImports(targetModel, relationName); - while (importsArray.length > 0) { - let currentImport = importsArray.pop(); - addCurrentImport(sourceFile, currentImport); + getRelationPropertyType() { + let propertyType = + this.capitalizeFirstLetter(this.artifactInfo.relationName); + if (this.artifactInfo.relationName == relationUtils.relationType.belongsTo) { + propertyType += "Accessor"; + } + else if (this.artifactInfo.relationName == relationType.hasOne || + this.artifactInfo.relationName == relationUtils.relationType.hasMany) { + propertyType += "RepositoryFactory" } + else { + throw Error("relation is invalid"); + } + propertyType = propertyType + + "<" + this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + + ", typeof " + + this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + + ".prototype.id>"; + + return (propertyType); } - getRequiredImports(targetModel, relationName) { - let capRelationName = capitalizeFirstLetter(relationName); + getClassName(fileName) { + let sourceFile = new ast.Project().addExistingSourceFile(fileName); + let className = sourceFile.getClasses()[0].getNameOrThrow(); + return className; + } - let importsArray = getSharedImports(targetModel); + handleImports() { + let requierdImports = this.getRequiredImports(); + this.addRequiredImports(requierdImports); + } - switch (relationName) { - case relationType.hasMany: + getRequiredImports() { + let importsArray = [{ + name: this.artifactInfo.dstModelClass, + module: "../models" + }, { + name: "repository", + module: "@loopback/repository" + }, { + name: "Getter", + module: "@loopback/core" + }, { + name: this.artifactInfo.dstRepositoryClassName, + module: "./index" + }]; + + let RelationName = + this.capitalizeFirstLetter(this.artifactInfo.relationName); + switch (this.artifactInfo.relationName) { + case (relationType.hasMany): importsArray.push({ - name: capRelationName + 'RepositoryFactory', - module: '@loopback/repository', + name: RelationName + "RepositoryFactory", + module: "@loopback/repository" }); break; - - case relationType.hasOne: + case (relationType.hasOne): importsArray.push({ - name: capRelationName + 'RepositoryFactory', - module: '@loopback/repository', + name: RelationName + "RepositoryFactory", + module: "@loopback/repository" }); break; - case relationType.belongsTo: + case (relationType.belongsTo): importsArray.push({ - name: capRelationName + 'Accessor', - module: '@loopback/repository', + name: RelationName + "Accessor", + module: "@loopback/repository" }); break; @@ -122,169 +171,141 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { return importsArray; } - getSharedImports(targetModel) { - let capTargetModel = capitalizeFirstLetter(targetModel); - let importsArray = [ - { - name: capTargetModel, - module: '../models', - }, - { - name: capTargetModel + 'Repository', - module: './index', - }, - { - name: 'repository', - module: '@loopback/repository', - }, - { - name: 'Getter', - module: '@loopback/core', - }, - ]; - return importsArray; + addRequiredImports(requiredImports) { + for (let currentImport of requiredImports) { + this.addImport(currentImport, this.artifactInfo.srcRepositoryFile); + } } - addCurrentImport(sourceFile, currentImport) { - if (!doesModuleExists(sourceFile, currentImport.module)) { - sourceFile.addImportDeclaration({ - moduleSpecifier: currentImport.module, - }); + addImport(requiredImport) { + if (!this.doesModuleExist(requiredImport)) { + this.addImportWithNonExistingModule(requiredImport); } - if (!doesImportExistInModule(sourceFile, currentImport)) { - sourceFile - .getImportDeclarationOrThrow(currentImport.module) - .addNamedImport(currentImport.name); + else { + this.addImportsWithExistingModule(requiredImport); } } - doesModuleExists(sourceFile, moduleName) { - return sourceFile.getImportDeclaration(moduleName); + addImportWithNonExistingModule(requiredImport) { + this.artifactInfo.srcRepositoryFile.addImportDeclaration({ + moduleSpecifier: requiredImport.module, + namedImports: [requiredImport.name] + }); } - doesImportExistInModule(sourceFile, currentImport) { - let identicalImport; - let relevantImports = getNamedImportsFromModule( - sourceFile, - currentImport.module, - ); - if (relevantImports.length > 0) { - identicalImport = relevantImports[0] - .getNamedImports() - .filter(imp => imp.getName() == currentImport.name); + addImportsWithExistingModule(requiredImport) { + let moduleName = requiredImport.module; + let importDeclcaration = + this.artifactInfo.srcRepositoryFile. + getImportDeclarationOrThrow(moduleName); + if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { + importDeclcaration.addNamedImport(requiredImport.name); } + } - return identicalImport && identicalImport.length > 0; + doesImportExist(importDelcaration, importName) { + let allNamedImports = importDelcaration.getNamedImports(); + for (let currentNamedImport of allNamedImports) { + if (currentNamedImport.getName() == importName) { + return true; + } + } + return false; } - getNamedImportsFromModule(sourceFile, moduleName) { - let allImports = sourceFile.getImportDeclarations(); - let relevantImports = allImports.filter( - imp => imp.getModuleSpecifierValue() == moduleName, - ); - return relevantImports; + doesModuleExist(importDeclaration) { + let moduleName = importDeclaration.module; + let relevantImport = this.artifactInfo.srcRepositoryFile. + getImportDeclaration(moduleName); + return (relevantImport != undefined); } - capitalizeFirstLetter(string) { - string = string[0].toUpperCase() + string.substring(1); - return string; + handleProperties() { + + let classDeclaration = + this.artifactInfo.srcRepositoryFile. + getClassOrThrow(this.artifactInfo.srcRepositoryClassName); + + this.addProperty(classDeclaration); + + this.orderProperties(classDeclaration); } - addRequiredProperties(sourceFile, sourceModel, targetModel, relationName) { - let classDeclaration = sourceFile.getClassOrThrow( - capitalizeFirstLetter(sourceModel) + 'Repository', - ); + addProperty(classDeclaration) { classDeclaration.addProperty({ scope: ast.Scope.Public, isReadonly: true, - name: getTargetPropertyName(targetModel, relationName), - type: getTargetPropertyType(sourceModel, targetModel, relationName), + name: this.artifactInfo.relationProperty.name, + type: this.artifactInfo.relationProperty.type, }); - orderProperties(classDeclaration); - } - - getTargetPropertyName(targetModel, relationName) { - let propertyName = targetModel[0].toLowerCase(); - propertyName += targetModel.substring(1); - - if (relationName == relationType.hasMany) { - propertyName += 's'; - } - return propertyName; - } - - getTargetPropertyType(sourceModel, targetModel, relationName) { - let propertyType = capitalizeFirstLetter(relationName); - if (relationName == relationType.belongsTo) { - propertyType += 'Accessor'; - } else if ( - relationName == relationType.hasOne || - relationName == relationType.hasMany - ) { - propertyType += 'RepositoryFactory'; - } else { - throw Error('relation is invalid'); - } - propertyType += - '<' + - capitalizeFirstLetter(targetModel) + - ', typeof ' + - capitalizeFirstLetter(sourceModel) + - '.prototype.id>'; - - return propertyType; } orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function(currentProperty) { + classDeclaration.getProperties().forEach(function (currentProperty) { currentProperty.setOrder(0); - }); + }) } - editConstructor(sourceFile, sourceModel, targetModel, relationName) { - let capSourceModel = capitalizeFirstLetter(sourceModel); - let classDeclaration = sourceFile.getClassOrThrow( - capSourceModel + 'Repository', - ); + handleConstructor() { + + let classDeclaration = + this.artifactInfo.srcRepositoryFile. + getClassOrThrow(this.artifactInfo.srcRepositoryClassName); let classConstructor = classDeclaration.getConstructors()[0]; - addParameters(classConstructor, targetModel); - addCreator(classConstructor, targetModel, relationName); + + this.addParameters(classConstructor); + + this.addCreator(classConstructor); } - addParameters(classConstructor, targetModel) { + addParameters(classConstructor) { classConstructor.addParameter({ - decorators: [ - { - name: 'repository.getter', - arguments: ["'" + capitalizeFirstLetter(targetModel) + "Repository'"], - }, - ], - name: targetModel + 'RepositoryGetter', - type: 'Getter<' + capitalizeFirstLetter(targetModel) + 'Repository>,', - }); + decorators: [{ + name: "repository.getter", + arguments: ["\'" + + this.artifactInfo.dstRepositoryClassName + "\'"] + }], + name: this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + + "Getter", + type: "Getter<" + this.artifactInfo.dstRepositoryClassName + ">,", + scope: ast.Scope.Protected + }) } - addCreator(classConstructor, targetModel, relationName) { - var propertyName = getTargetPropertyName(targetModel, relationName); - var method = 'this.create' + capitalizeFirstLetter(relationName); - if (relationName == relationType.belongsTo) { - method += 'Accessor'; - } else if ( - relationName == relationType.hasMany || - relationName == relationType.hasOne - ) { - method += 'RepositoryFactory'; - } else { - throw Error('relation is invalid'); + + addCreator(classConstructor) { + let statement = "this.create" + + this.capitalizeFirstLetter(this.artifactInfo.relationName); + if (this.artifactInfo.relationName == relationType.belongsTo) { + statement += "Accessor"; + } + else if (this.artifactInfo.relationName == relationType.hasMany || + this.artifactInfo.relationName == relationType.hasOne) { + statement += "RepositoryFactory"; + } + else { + throw Error("relation is invalid"); } - method += 'For('; + statement += "For("; - var parameter1 = "'" + propertyName + "',"; - var paramater2 = 'get' + capitalizeFirstLetter(targetModel) + 'Repository,'; + let parameter1 = "\'" + this.artifactInfo.relationProperty.name + "\',"; + let paramater2 = + this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + + "Getter,"; - let creatorStatement = - 'this.' + propertyName + '=' + method + parameter1 + paramater2 + ');'; + statement = "this." + + this.artifactInfo.relationProperty.name + "=" + statement + + parameter1 + paramater2 + ");"; - classConstructor.insertStatements(1, creatorStatement); + classConstructor.insertStatements(1, statement); } -}; + + capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + + regularizeFirstLetter(string) { + return string.charAt(0).toLowerCase() + string.slice(1); + } + +} From ca042441dd788d8aac94d371298e62de8b67973c Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 08:31:28 -0500 Subject: [PATCH 35/89] Running lint:fix. --- .../generators/relation/repositoryRelation.js | 253 ++++++++++-------- 1 file changed, 137 insertions(+), 116 deletions(-) diff --git a/packages/cli/generators/relation/repositoryRelation.js b/packages/cli/generators/relation/repositoryRelation.js index e7f5000b2b38..25c4d6de0b9d 100644 --- a/packages/cli/generators/relation/repositoryRelation.js +++ b/packages/cli/generators/relation/repositoryRelation.js @@ -5,24 +5,21 @@ const path = require('path'); const utils = require('../../lib/utils'); const relationUtils = require('./relationutils'); - module.exports = class RepositoryRelation extends ArtifactGenerator { - constructor(args, opts) { super(args, opts); } _setupGenerator() { - super._setupGenerator(); this.artifactInfo = { type: 'relation', - rootDir: utils.sourceRootDir + rootDir: utils.sourceRootDir, }; this.artifactInfo.repositoriesDir = path.resolve( this.artifactInfo.rootDir, - 'repositories' + 'repositories', ); this.artifactInfo.modelsDir = path.resolve( this.artifactInfo.rootDir, @@ -30,8 +27,12 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { ); } - generateRelationRepository(sourceModel, targetModel, - foreignKey, relationName) { + generateRelationRepository( + sourceModel, + targetModel, + foreignKey, + relationName, + ) { this.initializeProperties(sourceModel, targetModel, relationName); this.handleImports(); this.handleProperties(); @@ -40,47 +41,52 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { } initializeProperties(sourceModel, targetModel, relationName) { - this.artifactInfo.srcModelFile = path.resolve( this.artifactInfo.modelsDir, - sourceModel + ".model.ts"); + sourceModel + '.model.ts', + ); this.artifactInfo.dstModelFile = path.resolve( this.artifactInfo.modelsDir, - targetModel + ".model.ts"); + targetModel + '.model.ts', + ); - this.artifactInfo.srcModelClass = - this.getClassName(this.artifactInfo.srcModelFile); + this.artifactInfo.srcModelClass = this.getClassName( + this.artifactInfo.srcModelFile, + ); - this.artifactInfo.dstModelClass = - this.getClassName(this.artifactInfo.dstModelFile); + this.artifactInfo.dstModelClass = this.getClassName( + this.artifactInfo.dstModelFile, + ); this.artifactInfo.srcRepositoryFile = path.resolve( this.artifactInfo.repositoriesDir, - sourceModel + ".repository.ts" - ) + sourceModel + '.repository.ts', + ); this.artifactInfo.dstRepositoryFile = path.resolve( this.artifactInfo.repositoriesDir, - targetModel + ".repository.ts" - ) + targetModel + '.repository.ts', + ); - this.artifactInfo.srcRepositoryClassName = - this.getClassName(this.artifactInfo.srcRepositoryFile); + this.artifactInfo.srcRepositoryClassName = this.getClassName( + this.artifactInfo.srcRepositoryFile, + ); - this.artifactInfo.dstRepositoryClassName = - this.getClassName(this.artifactInfo.dstRepositoryFile); + this.artifactInfo.dstRepositoryClassName = this.getClassName( + this.artifactInfo.dstRepositoryFile, + ); this.artifactInfo.relationName = relationName; - this.artifactInfo.relationProperty = { name: this.getRelationPropertyName(), - type: this.getRelationPropertyType() - } + type: this.getRelationPropertyType(), + }; - this.artifactInfo.srcRepositoryFile = new ast.Project(). - addExistingSourceFile(this.artifactInfo.srcRepositoryFile); + this.artifactInfo.srcRepositoryFile = new ast.Project().addExistingSourceFile( + this.artifactInfo.srcRepositoryFile, + ); } getRelationPropertyName() { @@ -88,31 +94,36 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { propertyName += this.artifactInfo.dstModelClass.substring(1); if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyName += "s"; + propertyName += 's'; } return propertyName; } getRelationPropertyType() { - let propertyType = - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationUtils.relationType.belongsTo) { - propertyType += "Accessor"; - } - else if (this.artifactInfo.relationName == relationType.hasOne || - this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyType += "RepositoryFactory" - } - else { - throw Error("relation is invalid"); + let propertyType = this.capitalizeFirstLetter( + this.artifactInfo.relationName, + ); + if ( + this.artifactInfo.relationName == relationUtils.relationType.belongsTo + ) { + propertyType += 'Accessor'; + } else if ( + this.artifactInfo.relationName == relationType.hasOne || + this.artifactInfo.relationName == relationUtils.relationType.hasMany + ) { + propertyType += 'RepositoryFactory'; + } else { + throw Error('relation is invalid'); } - propertyType = propertyType + - "<" + this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + - ", typeof " + + propertyType = + propertyType + + '<' + + this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + + ', typeof ' + this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + - ".prototype.id>"; + '.prototype.id>'; - return (propertyType); + return propertyType; } getClassName(fileName) { @@ -127,40 +138,46 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { } getRequiredImports() { - let importsArray = [{ - name: this.artifactInfo.dstModelClass, - module: "../models" - }, { - name: "repository", - module: "@loopback/repository" - }, { - name: "Getter", - module: "@loopback/core" - }, { - name: this.artifactInfo.dstRepositoryClassName, - module: "./index" - }]; - - let RelationName = - this.capitalizeFirstLetter(this.artifactInfo.relationName); + let importsArray = [ + { + name: this.artifactInfo.dstModelClass, + module: '../models', + }, + { + name: 'repository', + module: '@loopback/repository', + }, + { + name: 'Getter', + module: '@loopback/core', + }, + { + name: this.artifactInfo.dstRepositoryClassName, + module: './index', + }, + ]; + + let RelationName = this.capitalizeFirstLetter( + this.artifactInfo.relationName, + ); switch (this.artifactInfo.relationName) { - case (relationType.hasMany): + case relationType.hasMany: importsArray.push({ - name: RelationName + "RepositoryFactory", - module: "@loopback/repository" + name: RelationName + 'RepositoryFactory', + module: '@loopback/repository', }); break; - case (relationType.hasOne): + case relationType.hasOne: importsArray.push({ - name: RelationName + "RepositoryFactory", - module: "@loopback/repository" + name: RelationName + 'RepositoryFactory', + module: '@loopback/repository', }); break; - case (relationType.belongsTo): + case relationType.belongsTo: importsArray.push({ - name: RelationName + "Accessor", - module: "@loopback/repository" + name: RelationName + 'Accessor', + module: '@loopback/repository', }); break; @@ -180,8 +197,7 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { addImport(requiredImport) { if (!this.doesModuleExist(requiredImport)) { this.addImportWithNonExistingModule(requiredImport); - } - else { + } else { this.addImportsWithExistingModule(requiredImport); } } @@ -189,15 +205,15 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { addImportWithNonExistingModule(requiredImport) { this.artifactInfo.srcRepositoryFile.addImportDeclaration({ moduleSpecifier: requiredImport.module, - namedImports: [requiredImport.name] + namedImports: [requiredImport.name], }); } addImportsWithExistingModule(requiredImport) { let moduleName = requiredImport.module; - let importDeclcaration = - this.artifactInfo.srcRepositoryFile. - getImportDeclarationOrThrow(moduleName); + let importDeclcaration = this.artifactInfo.srcRepositoryFile.getImportDeclarationOrThrow( + moduleName, + ); if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { importDeclcaration.addNamedImport(requiredImport.name); } @@ -215,16 +231,16 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { doesModuleExist(importDeclaration) { let moduleName = importDeclaration.module; - let relevantImport = this.artifactInfo.srcRepositoryFile. - getImportDeclaration(moduleName); - return (relevantImport != undefined); + let relevantImport = this.artifactInfo.srcRepositoryFile.getImportDeclaration( + moduleName, + ); + return relevantImport != undefined; } handleProperties() { - - let classDeclaration = - this.artifactInfo.srcRepositoryFile. - getClassOrThrow(this.artifactInfo.srcRepositoryClassName); + let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( + this.artifactInfo.srcRepositoryClassName, + ); this.addProperty(classDeclaration); @@ -241,16 +257,15 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { } orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function (currentProperty) { + classDeclaration.getProperties().forEach(function(currentProperty) { currentProperty.setOrder(0); - }) + }); } handleConstructor() { - - let classDeclaration = - this.artifactInfo.srcRepositoryFile. - getClassOrThrow(this.artifactInfo.srcRepositoryClassName); + let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( + this.artifactInfo.srcRepositoryClassName, + ); let classConstructor = classDeclaration.getConstructors()[0]; this.addParameters(classConstructor); @@ -260,42 +275,49 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { addParameters(classConstructor) { classConstructor.addParameter({ - decorators: [{ - name: "repository.getter", - arguments: ["\'" + - this.artifactInfo.dstRepositoryClassName + "\'"] - }], - name: this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - "Getter", - type: "Getter<" + this.artifactInfo.dstRepositoryClassName + ">,", - scope: ast.Scope.Protected - }) + decorators: [ + { + name: 'repository.getter', + arguments: ["'" + this.artifactInfo.dstRepositoryClassName + "'"], + }, + ], + name: + this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + + 'Getter', + type: 'Getter<' + this.artifactInfo.dstRepositoryClassName + '>,', + scope: ast.Scope.Protected, + }); } - addCreator(classConstructor) { - let statement = "this.create" + + let statement = + 'this.create' + this.capitalizeFirstLetter(this.artifactInfo.relationName); if (this.artifactInfo.relationName == relationType.belongsTo) { - statement += "Accessor"; - } - else if (this.artifactInfo.relationName == relationType.hasMany || - this.artifactInfo.relationName == relationType.hasOne) { - statement += "RepositoryFactory"; + statement += 'Accessor'; + } else if ( + this.artifactInfo.relationName == relationType.hasMany || + this.artifactInfo.relationName == relationType.hasOne + ) { + statement += 'RepositoryFactory'; + } else { + throw Error('relation is invalid'); } - else { - throw Error("relation is invalid"); - } - statement += "For("; + statement += 'For('; - let parameter1 = "\'" + this.artifactInfo.relationProperty.name + "\',"; + let parameter1 = "'" + this.artifactInfo.relationProperty.name + "',"; let paramater2 = this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - "Getter,"; + 'Getter,'; - statement = "this." + - this.artifactInfo.relationProperty.name + "=" + statement + - parameter1 + paramater2 + ");"; + statement = + 'this.' + + this.artifactInfo.relationProperty.name + + '=' + + statement + + parameter1 + + paramater2 + + ');'; classConstructor.insertStatements(1, statement); } @@ -307,5 +329,4 @@ module.exports = class RepositoryRelation extends ArtifactGenerator { regularizeFirstLetter(string) { return string.charAt(0).toLowerCase() + string.slice(1); } - -} +}; From 73e8325eb46a6ef590a99defeebca89d69e9c2be Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 08:54:18 -0500 Subject: [PATCH 36/89] Code refactoring. --- packages/cli/generators/relation/index.js | 13 +++++++------ .../controller-relation-template-has-many.ts.ejs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 150f6c4a4058..5b87c84cddfb 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -4,6 +4,7 @@ // License text available at https://opensource.org/licenses/MIT 'use strict'; + const _ = require('lodash'); const ArtifactGenerator = require('../../lib/artifact-generator'); const debug = require('../../lib/debug')('relation-generator'); @@ -21,9 +22,9 @@ const ModelRelation = require('./modelRelation'); const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; -const PROMPT_MESSAGE__SOURCE_MODEL = 'Please select source model'; -const PROMPT_MESSAGE__TARGET__MODEL = 'Please select target model'; -const PROMPT_MESSAGE__PROPERTY_NAME = 'Property name for the relation'; +const PROMPT_MESSAGE_SOURCE_MODEL = 'Please select source model'; +const PROMPT_MESSAGE_TARGET_MODEL = 'Please select target model'; +const PROMPT_MESSAGE_PROPERTY_NAME = 'Property name for the relation'; const RELATION_TYPE_BELONGS_TO = 'belongsTo'; const RELATION_TYPE_HAS_MANY = 'hasMany'; @@ -243,7 +244,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { if (this.shouldExit()) return false; return await this._promptModelList( - PROMPT_MESSAGE__SOURCE_MODEL, + PROMPT_MESSAGE_SOURCE_MODEL, 'sourceModel', ); } @@ -253,7 +254,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { if (this.shouldExit()) return false; return await this._promptModelList( - PROMPT_MESSAGE__TARGET__MODEL, + PROMPT_MESSAGE_TARGET_MODEL, 'destinationModel', ); } @@ -313,7 +314,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { { type: 'string', name: 'value', - message: PROMPT_MESSAGE__PROPERTY_NAME, + message: PROMPT_MESSAGE_PROPERTY_NAME, default: defaultRelationName, when: !this.artifactInfo.relationName, }, diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs index 33d3bb292dfb..62716edf8534 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -50,7 +50,7 @@ export class <%= controllerClassName %> { }, }) async create( - @param.path.<%= foreignKeyType %>('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, + @param.path.<%= foreignKeyType %>('id') id: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, @requestBody() <%= targetModelName %>: <%= targetModelClassName %>, ): Promise<<%= targetModelClassName %>> { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(<%= relationPropertyName %>).create(<%= targetModelName %>); From f56e58e6b6ecf85f16b38bcf792ef1cb2f4e3374 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 09:01:30 -0500 Subject: [PATCH 37/89] Code refactoring. --- packages/cli/generators/relation/index.js | 74 +++++++++++------------ 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 5b87c84cddfb..f3242fd454f6 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -167,20 +167,27 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); } - // Prompt a user for Relation type - async promptRelationBaseClassName() { - this.artifactInfo.relationType = await this.prompt([ - { - type: 'list', - name: 'relationBaseClass', - message: PROMPT_BASE_RELATION_CLASS, - choices: availableRelationsBaseClasses, - when: !this.artifactInfo.availableRelationsBaseClasses, - validate: utils.validateClassName, - }, - ]); - this.options.relationType = this.artifactInfo.relationType.relationBaseClass; - return this.artifactInfo.relationType; + _getDefaultRelationName() { + var defaultRelationName; + switch (this.options.relationType) { + case RELATION_TYPE_BELONGS_TO: + defaultRelationName = + utils.camelCase(this.options.destinationModel) + + utils.toClassName(this.options.foreignKey); + break; + case RELATION_TYPE_HAS_MANY: + defaultRelationName = utils.pluralize( + utils.camelCase(this.options.destinationModel), + ); + break; + case RELATION_TYPE_HAS_ONE: + defaultRelationName = utils.camelCase(this.options.destinationModel); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + + return defaultRelationName; } async _promptModelList(message, parameter) { @@ -239,6 +246,22 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return this.artifactInfo[parameter]; } + // Prompt a user for Relation type + async promptRelationBaseClassName() { + this.artifactInfo.relationType = await this.prompt([ + { + type: 'list', + name: 'relationBaseClass', + message: PROMPT_BASE_RELATION_CLASS, + choices: availableRelationsBaseClasses, + when: !this.artifactInfo.availableRelationsBaseClasses, + validate: utils.validateClassName, + }, + ]); + this.options.relationType = this.artifactInfo.relationType.relationBaseClass; + return this.artifactInfo.relationType; + } + // Get model list for source model. async promptSourceModels() { if (this.shouldExit()) return false; @@ -324,27 +347,4 @@ module.exports = class RelationGenerator extends ArtifactGenerator { //Generate this repository await this._scaffold(); } - - _getDefaultRelationName() { - var defaultRelationName; - switch (this.options.relationType) { - case RELATION_TYPE_BELONGS_TO: - defaultRelationName = - utils.camelCase(this.options.destinationModel) + - utils.toClassName(this.options.foreignKey); - break; - case RELATION_TYPE_HAS_MANY: - defaultRelationName = utils.pluralize( - utils.camelCase(this.options.destinationModel), - ); - break; - case RELATION_TYPE_HAS_ONE: - defaultRelationName = utils.camelCase(this.options.destinationModel); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - - return defaultRelationName; - } }; From b49c814a73ee672078c5c09c75acd5d32441f90d Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 09:06:55 -0500 Subject: [PATCH 38/89] cli relation index.js: removed redundant code. --- packages/cli/generators/relation/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index f3242fd454f6..e337502ced69 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -137,13 +137,11 @@ module.exports = class RelationGenerator extends ArtifactGenerator { //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathModel; //model.generateRelationModel(this.options); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); - this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathRepo; repo.generateRelationRepository( this.options.sourceModel, From 3a62abefadcb3962af3f8fd922720dd7075e67da Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Sun, 10 Feb 2019 16:22:43 +0200 Subject: [PATCH 39/89] feat(cli): replaced multiple properties with options --- .../cli/generators/relation/modelRelation.js | 188 +++++++++++------- 1 file changed, 118 insertions(+), 70 deletions(-) diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index b593d2f8b458..cea6205f9007 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -1,4 +1,4 @@ -"use strict"; +'use strict'; const ArtifactGenerator = require('../../lib/artifact-generator'); const debug = require('../../lib/debug')('relation-generator'); const inspect = require('util').inspect; @@ -8,7 +8,6 @@ const utils = require('../../lib/utils'); const ast = require('ts-simple-ast'); module.exports = class ModelRelation extends ArtifactGenerator { - _setupGenerator() { super._setupGenerator(); this.artifactInfo = { @@ -22,13 +21,27 @@ module.exports = class ModelRelation extends ArtifactGenerator { ); } - generateRelationModel(sourceModel, targetModel, foreignKey, relationType, relationName) { + generateRelationModel(options + // sourceModel, + // targetModel, + // foreignKey, + // relationType, + // relationName, + ) { let modelPath = this.artifactInfo.modelDir; - this.generateModel(sourceModel, targetModel, relationType, modelPath, foreignKey, relationName); + this.generateModel( + this.options.sourceModel, + this.options.destinationModel, + this.options.relationType, + modelPath, + this.options.foreignKey, + this.options.relationName, + this.options.foreignKeyType, + ); } addFileToProject(project, path, modelName) { - const fileName = path + "/" + modelName + '.model.ts'; + const fileName = path + '/' + modelName + '.model.ts'; return project.addExistingSourceFile(fileName); } @@ -41,7 +54,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { } isClassExist(fileName) { - return (this.getClassesCount(fileName) == 1); + return this.getClassesCount(fileName) == 1; } getPropertiesCount(classObj) { @@ -49,31 +62,54 @@ module.exports = class ModelRelation extends ArtifactGenerator { } getPropertyStartPos(classObj) { - return classObj.getChildSyntaxList().getChildAtIndex(this.getPropertiesCount(classObj) - 1).getPos() + return classObj + .getChildSyntaxList() + .getChildAtIndex(this.getPropertiesCount(classObj) - 1) + .getPos(); } getClassProperties(classObj) { - return classObj.getProperties() + return classObj.getProperties(); } isPropertyExist(classObj, propertyName) { - return this.getClassProperties(classObj).map(x => x.getName()).includes(propertyName) + return this.getClassProperties(classObj) + .map(x => x.getName()) + .includes(propertyName); } getType(classObj, propertyName) { - return classObj.getProperty(propertyName).getType().getText() - } - generateModel(sourceModel, targetModel, relationType, path, foreignKey, relationName) { - + return classObj + .getProperty(propertyName) + .getType() + .getText(); + } + generateModel( + sourceModel, + targetModel, + relationType, + path, + foreignKey, + relationName, + fktype, + ) { let project = new ast.Project(); - const sourceFile = this.addFileToProject(project, path, utils.kebabCase(sourceModel)); + const sourceFile = this.addFileToProject( + project, + path, + utils.kebabCase(sourceModel), + ); if (!this.isClassExist(sourceFile)) { return; } const sourceClass = this.getClassObj(sourceFile, sourceModel); - const targetFile = this.addFileToProject(project, path, utils.kebabCase(targetModel)); + const targetFile = this.addFileToProject( + project, + path, + utils.kebabCase(targetModel), + ); if (!this.isClassExist(targetFile)) { return; } @@ -81,92 +117,97 @@ module.exports = class ModelRelation extends ArtifactGenerator { let modelProperty; switch (relationType) { - case "hasMany": - if (this.isPropertyExist((sourceClass), relationName)) { - console.log('property ' + relationName + ' exsist in the model') - throw new Error(' Property exsists') - } - else { + case 'hasMany': + if (this.isPropertyExist(sourceClass, relationName)) { + console.log('property ' + relationName + ' exsist in the model'); + throw new Error(' Property exsists'); + } else { modelProperty = this.getHasMany(targetModel, relationName); - } break; - case "hasOne": - if (this.isPropertyExist((sourceClass), relationName)) { - console.log('property ' + relationName + ' exsist in the model') - throw new Error(' Property exsists') - } - else { + case 'hasOne': + if (this.isPropertyExist(sourceClass, relationName)) { + console.log('property ' + relationName + ' exsist in the model'); + throw new Error(' Property exsists'); + } else { modelProperty = this.getHasOne(targetModel, relationName); } break; - case "belongsTo": + case 'belongsTo': //fix remvove ID - if (this.isPropertyExist((sourceClass), (relationName + 'Id'))) { - console.log('property ' + relationName + 'Id exsist in the model') - throw new Error(' Property exsists') - } - else { - modelProperty = this.getBelongsTo(targetModel, relationName, 'Number'); + if (this.isPropertyExist(sourceClass, relationName + 'Id')) { + console.log('property ' + relationName + 'Id exsist in the model'); + throw new Error(' Property exsists'); + } else { + modelProperty = this.getBelongsTo( + targetModel, + relationName, + 'Number', + ); } break; } - sourceClass.insertProperty(this.getPropertiesCount(sourceClass), modelProperty); - sourceClass.insertText(this.getPropertyStartPos(sourceClass), "\n") + sourceClass.insertProperty( + this.getPropertiesCount(sourceClass), + modelProperty, + ); + sourceClass.insertText(this.getPropertyStartPos(sourceClass), '\n'); this.addRequiredImports(sourceFile, targetModel, relationType, targetModel); - sourceClass.formatText() + sourceClass.formatText(); sourceFile.save(); } - getHasMany(className, relationName) { let relationProperty = { - decorators: [{ name: "hasMany", arguments: ['() => ' + className] }], + decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], name: relationName, - type: className + "[]", - } + type: className + '[]', + }; - return (relationProperty) + return relationProperty; } getHasOne(className, relationName) { let relationProperty = { - decorators: [{ name: "hasOne", arguments: ['() => ' + className] }], + decorators: [{ name: 'hasOne', arguments: ['() => ' + className] }], name: relationName, type: className, - } - return (relationProperty) + }; + return relationProperty; } getBelongsTo(className, relationName, fktype) { - let relationProperty + let relationProperty; relationProperty = { - decorators: [{ name: "belongsTo", arguments: ['() => ' + className] }], + decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], name: relationName + 'Id', type: fktype, - } - return (relationProperty) + }; + return relationProperty; } - addRequiredImports(sourceFile, targetModel, relationType, targetClassName) { - let importsArray = this.getRequiredImports(targetModel, relationType, targetClassName); + let importsArray = this.getRequiredImports( + targetModel, + relationType, + targetClassName, + ); while (importsArray.length > 0) { let currentImport = importsArray.pop(); this.addCurrentImport(sourceFile, currentImport); } } - getRequiredImports(targetModel, relationType, targetClassName) { - - let importsArray = [{ - name: targetClassName, - module: "./" + targetModel + ".model" - }, { - name: relationType, - module: "@loopback/repository" - }, + let importsArray = [ + { + name: targetClassName, + module: './' + targetModel + '.model', + }, + { + name: relationType, + module: '@loopback/repository', + }, ]; return importsArray; @@ -175,33 +216,40 @@ module.exports = class ModelRelation extends ArtifactGenerator { addCurrentImport(sourceFile, currentImport) { if (!this.doesModuleExists(sourceFile, currentImport.module)) { sourceFile.addImportDeclaration({ - moduleSpecifier: currentImport.module + moduleSpecifier: currentImport.module, }); } if (!this.doesImportExistInModule(sourceFile, currentImport)) { - sourceFile.getImportDeclarationOrThrow(currentImport.module).addNamedImport(currentImport.name); + sourceFile + .getImportDeclarationOrThrow(currentImport.module) + .addNamedImport(currentImport.name); } } - doesModuleExists(sourceFile, moduleName) { return sourceFile.getImportDeclaration(moduleName); } doesImportExistInModule(sourceFile, currentImport) { let identicalImport; - let relevantImports = this.getNamedImportsFromModule(sourceFile, currentImport.module); + let relevantImports = this.getNamedImportsFromModule( + sourceFile, + currentImport.module, + ); if (relevantImports.length > 0) { - identicalImport = relevantImports[0].getNamedImports().filter(imp => imp.getName() == currentImport.name); + identicalImport = relevantImports[0] + .getNamedImports() + .filter(imp => imp.getName() == currentImport.name); } - return (identicalImport && identicalImport.length > 0); + return identicalImport && identicalImport.length > 0; } getNamedImportsFromModule(sourceFile, moduleName) { let allImports = sourceFile.getImportDeclarations(); - let relevantImports = allImports.filter(imp => imp.getModuleSpecifierValue() == moduleName); + let relevantImports = allImports.filter( + imp => imp.getModuleSpecifierValue() == moduleName, + ); return relevantImports; } - -} +}; From e739fdb90e29ad48cef340389ad0df3ca24ce90c Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Sun, 10 Feb 2019 16:49:53 +0200 Subject: [PATCH 40/89] fixed import --- packages/cli/generators/relation/modelRelation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index dfcaf02707ad..841c98ebe696 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -196,7 +196,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { let importsArray = [ { name: targetClassName, - module: './' + targetModel + '.model', + module: './' + utils.kebabCase(targetModel) + '.model', }, { name: relationType, From 4654e28876c88fd42c2581a496caa58cb7b7ebbd Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 10:08:37 -0500 Subject: [PATCH 41/89] Fixed belongsto controller. --- packages/cli/generators/relation/controllerRelation.js | 1 + packages/cli/generators/relation/index.js | 8 ++++---- .../controller-relation-template-belongsto.ts.ejs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 5babd641bc21..034d5c0d89cf 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -63,6 +63,7 @@ module.exports = class ControllerRelation extends ArtifactGenerator { generateControllerRelationBelongsTo(options) { this.artifactInfo.sourceModelClassName = options.sourceModel; this.artifactInfo.targetModelClassName = options.destinationModel; + this.artifactInfo.paramTargetModel = utils.camelCase(options.destinationModel); this.artifactInfo.sourceRepositoryClassName = this.artifactInfo.sourceModelClassName + 'Repository'; this.artifactInfo.controllerClassName = diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index e337502ced69..86f26f4128ec 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -138,7 +138,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); this.artifactInfo.relPath = relPathModel; - //model.generateRelationModel(this.options); + model.generateRelationModel(this.options); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); @@ -224,8 +224,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -297,7 +297,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { name: 'propertyName', message: `Please enter the name of the ID property for ${ this.artifactInfo.sourceModel - }:`, + }:`, default: 'id', }, ]; diff --git a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs index a358f9943ce4..17583f9e0de7 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs @@ -18,6 +18,6 @@ export class <%= controllerClassName %> { async get<%= targetModelClassName %>( @param.path.<%= foreignKeyType %>('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, ): Promise<<%= targetModelClassName %>> { - return await this.<%= paramSourceRepository %>.<%= targetModelName %>(<%= relationPropertyName %>); + return await this.<%= paramSourceRepository %>.<%= paramTargetModel %>(<%= relationPropertyName %>); } } From 512e3c37cd272b79ed6cfa9bd58c33108392403e Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 10:10:54 -0500 Subject: [PATCH 42/89] Running lint:fix. --- packages/cli/generators/relation/controllerRelation.js | 4 +++- packages/cli/generators/relation/index.js | 6 +++--- packages/cli/generators/relation/modelRelation.js | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 034d5c0d89cf..39f7f04dd7c8 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -63,7 +63,9 @@ module.exports = class ControllerRelation extends ArtifactGenerator { generateControllerRelationBelongsTo(options) { this.artifactInfo.sourceModelClassName = options.sourceModel; this.artifactInfo.targetModelClassName = options.destinationModel; - this.artifactInfo.paramTargetModel = utils.camelCase(options.destinationModel); + this.artifactInfo.paramTargetModel = utils.camelCase( + options.destinationModel, + ); this.artifactInfo.sourceRepositoryClassName = this.artifactInfo.sourceModelClassName + 'Repository'; this.artifactInfo.controllerClassName = diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 86f26f4128ec..e9049ced3e2d 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -224,8 +224,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -297,7 +297,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { name: 'propertyName', message: `Please enter the name of the ID property for ${ this.artifactInfo.sourceModel - }:`, + }:`, default: 'id', }, ]; diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index dfcaf02707ad..d0cf9d271c75 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -153,7 +153,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getHasMany(className, relationName) { let relationProperty = { - decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], + decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], name: relationName, type: className + '[]', }; @@ -163,7 +163,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getHasOne(className, relationName) { let relationProperty = { - decorators: [{ name: 'hasOne', arguments: ['() => ' + className] }], + decorators: [{name: 'hasOne', arguments: ['() => ' + className]}], name: relationName, type: className, }; @@ -173,7 +173,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getBelongsTo(className, relationName, fktype) { let relationProperty; relationProperty = { - decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], + decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], name: relationName + 'Id', type: fktype, }; From 4f2016d5446b98775f7c316fca9971569dc22f58 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 10:22:05 -0500 Subject: [PATCH 43/89] Removed RELATION_TYPE consts. --- packages/cli/generators/relation/index.js | 33 +++++++------------ .../cli/generators/relation/relationutils.js | 4 +-- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index e9049ced3e2d..46d81306ab34 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -14,6 +14,7 @@ const chalk = require('chalk'); const utils = require('../../lib/utils'); const tsquery = require('../../lib/ast-helper'); const ast = require('ts-simple-ast'); +const relationUtils = require('./relationutils'); const ControllerRelation = require('./controllerRelation'); const RepositoryRelation = require('./repositoryRelation'); @@ -26,16 +27,6 @@ const PROMPT_MESSAGE_SOURCE_MODEL = 'Please select source model'; const PROMPT_MESSAGE_TARGET_MODEL = 'Please select target model'; const PROMPT_MESSAGE_PROPERTY_NAME = 'Property name for the relation'; -const RELATION_TYPE_BELONGS_TO = 'belongsTo'; -const RELATION_TYPE_HAS_MANY = 'hasMany'; -const RELATION_TYPE_HAS_ONE = 'hasOne'; - -const availableRelationsBaseClasses = [ - RELATION_TYPE_BELONGS_TO, - RELATION_TYPE_HAS_MANY, - RELATION_TYPE_HAS_ONE, -]; - const relPathControllersFolder = '/controllers'; const relPathModelsFolder = '/models'; const relPathRepositoriesFolder = '/repositories'; @@ -121,13 +112,13 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.artifactInfo.relPath = relPathCtrl; switch (this.options.relationType) { - case RELATION_TYPE_BELONGS_TO: + case relationUtils.relationType.belongsTo: ctrl.generateControllerRelationBelongsTo(this.options); break; - case RELATION_TYPE_HAS_MANY: + case relationUtils.relationType.hasMany: ctrl.generateControllerRelationHasMany(this.options); break; - case RELATION_TYPE_HAS_ONE: + case relationUtils.relationType.hasOne: ctrl.generateControllerRelationHasOne(this.options); break; default: @@ -168,17 +159,17 @@ module.exports = class RelationGenerator extends ArtifactGenerator { _getDefaultRelationName() { var defaultRelationName; switch (this.options.relationType) { - case RELATION_TYPE_BELONGS_TO: + case relationUtils.relationType.belongsTo: defaultRelationName = utils.camelCase(this.options.destinationModel) + utils.toClassName(this.options.foreignKey); break; - case RELATION_TYPE_HAS_MANY: + case relationUtils.relationType.hasMany: defaultRelationName = utils.pluralize( utils.camelCase(this.options.destinationModel), ); break; - case RELATION_TYPE_HAS_ONE: + case relationUtils.relationType.hasOne: defaultRelationName = utils.camelCase(this.options.destinationModel); break; default: @@ -224,8 +215,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -251,8 +242,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { type: 'list', name: 'relationBaseClass', message: PROMPT_BASE_RELATION_CLASS, - choices: availableRelationsBaseClasses, - when: !this.artifactInfo.availableRelationsBaseClasses, + choices: Object.keys(relationUtils.relationType), + when: !this.artifactInfo.relationType, validate: utils.validateClassName, }, ]); @@ -297,7 +288,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { name: 'propertyName', message: `Please enter the name of the ID property for ${ this.artifactInfo.sourceModel - }:`, + }:`, default: 'id', }, ]; diff --git a/packages/cli/generators/relation/relationutils.js b/packages/cli/generators/relation/relationutils.js index 10ce7dc274a5..dba9fa176dd5 100644 --- a/packages/cli/generators/relation/relationutils.js +++ b/packages/cli/generators/relation/relationutils.js @@ -1,5 +1,5 @@ exports.relationType = relationType = { - hasOne: 'hasOne', - hasMany: 'hasMany', belongsTo: 'belongsTo', + hasMany: 'hasMany', + hasOne: 'hasOne', }; From 602944058c83a77698a1e15368e37ae979893ed8 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 11:04:31 -0500 Subject: [PATCH 44/89] Fixed hasmany controller template: create call. --- .../templates/controller-relation-template-has-many.ts.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs index 62716edf8534..4d04f8fee5f2 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -53,7 +53,7 @@ export class <%= controllerClassName %> { @param.path.<%= foreignKeyType %>('id') id: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, @requestBody() <%= targetModelName %>: <%= targetModelClassName %>, ): Promise<<%= targetModelClassName %>> { - return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(<%= relationPropertyName %>).create(<%= targetModelName %>); + return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).create(<%= targetModelName %>); } @patch('/<%= sourceModelName %>s/{id}/<%= targetModelName %>s', { From b5b7b1805c55ed385cf9b768c14e2a9de7822fa5 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 10 Feb 2019 11:34:05 -0500 Subject: [PATCH 45/89] Add comment. --- packages/cli/generators/relation/index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 46d81306ab34..ef068a9d83c6 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -129,7 +129,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); this.artifactInfo.relPath = relPathModel; - model.generateRelationModel(this.options); + //model.generateRelationModel(this.options); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); @@ -271,7 +271,14 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); } - //Prompt ID + /** + * Prompt Foreign key if not exist: + * 1. From source model get primary key. If primary key does not exist error. + * 2. Get primary key type from source model. + * 3. Generate foreign key (camelCase source class Name + primary key name). + * 4. Check is foreign key exist in destination model. If not - prompt. + * Error - if type is not the same. + */ async promptModelId() { if (this.shouldExit()) return false; let idProperty; From 09afc5ab838f8ab902390e38b283f6456151bd56 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Mon, 11 Feb 2019 00:20:40 +0200 Subject: [PATCH 46/89] Fixed model to Foreign key --- packages/cli/generators/relation/index.js | 32 +++---- .../cli/generators/relation/modelRelation.js | 89 +++++++++++++------ 2 files changed, 80 insertions(+), 41 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index ef068a9d83c6..1523ee883eea 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -110,26 +110,26 @@ module.exports = class RelationGenerator extends ArtifactGenerator { let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; - - switch (this.options.relationType) { - case relationUtils.relationType.belongsTo: - ctrl.generateControllerRelationBelongsTo(this.options); - break; - case relationUtils.relationType.hasMany: - ctrl.generateControllerRelationHasMany(this.options); - break; - case relationUtils.relationType.hasOne: - ctrl.generateControllerRelationHasOne(this.options); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - + /* + switch (this.options.relationType) { + case relationUtils.relationType.belongsTo: + ctrl.generateControllerRelationBelongsTo(this.options); + break; + case relationUtils.relationType.hasMany: + ctrl.generateControllerRelationHasMany(this.options); + break; + case relationUtils.relationType.hasOne: + ctrl.generateControllerRelationHasOne(this.options); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + */ //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); this.artifactInfo.relPath = relPathModel; - //model.generateRelationModel(this.options); + model.generateRelationModel(this.options); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 9cbe331e516c..ea9559b1775f 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -72,21 +72,55 @@ module.exports = class ModelRelation extends ArtifactGenerator { .includes(propertyName); } + isRelationExist(classObj, propertyName) { + if (this.isForeignKeyExist(classObj, propertyName)) { + console.log('property ' + propertyName + ' exsist in the model'); + throw new Error(' Property exsists'); + } + return + } + + + isForeignKeyExist(classObj, foreignKey) { + return this.isPropertyExist(classObj, foreignKey) + } + getType(classObj, propertyName) { return classObj .getProperty(propertyName) .getType() .getText(); } + + isDefaultForeignKey(classObj, sourceModelPrimaryKey, foreignKey) { + let defaultForeignKey = utils.camelCase(classObj.getName()) + utils.toClassName(sourceModelPrimaryKey) + if (defaultForeignKey === foreignKey) { + console.log('default foreignKey is missing in the target model'); + throw new Error(' missing foreginKey'); + } + return + } + + addPropertyToModel(classOBj, modelProperty) { + classOBj.insertProperty( + this.getPropertiesCount(classOBj), + modelProperty, + ); + classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); + } + generateModel( sourceModel, targetModel, relationType, path, - foreignKey, + sourceModelPrimaryKey, relationName, fktype, + foreignKey ) { + let sourceModelPrimaryKeyType = 'number' + foreignKey = 'todoLId' let project = new ast.Project(); const sourceFile = this.addFileToProject( @@ -112,12 +146,15 @@ module.exports = class ModelRelation extends ArtifactGenerator { switch (relationType) { case 'hasMany': - if (this.isPropertyExist(sourceClass, relationName)) { - console.log('property ' + relationName + ' exsist in the model'); - throw new Error(' Property exsists'); - } else { - modelProperty = this.getHasMany(targetModel, relationName); + this.isRelationExist(sourceClass, relationName) + if (!(this.isForeignKeyExist(targetClass, foreignKey))) { + this.isDefaultForeignKey(sourceClass, sourceModelPrimaryKey, foreignKey) + modelProperty = this.addForeginKey(foreignKey, sourceModelPrimaryKeyType) + this.addPropertyToModel(targetClass, modelProperty); + targetClass.formatText(); + targetFile.save(); } + modelProperty = this.getHasMany(targetModel, relationName); break; case 'hasOne': if (this.isPropertyExist(sourceClass, relationName)) { @@ -129,31 +166,33 @@ module.exports = class ModelRelation extends ArtifactGenerator { break; case 'belongsTo': //fix remvove ID - if (this.isPropertyExist(sourceClass, relationName + 'Id')) { - console.log('property ' + relationName + 'Id exsist in the model'); - throw new Error(' Property exsists'); - } else { - modelProperty = this.getBelongsTo( - targetModel, - relationName, - 'Number', - ); - } + this.isRelationExist(sourceClass, relationName) + modelProperty = this.getBelongsTo( + targetModel, + relationName, + utils.toClassName(sourceModelPrimaryKeyType), + ) break; } - sourceClass.insertProperty( - this.getPropertiesCount(sourceClass), - modelProperty, - ); - sourceClass.insertText(this.getPropertyStartPos(sourceClass), '\n'); + + this.addPropertyToModel(sourceClass, modelProperty) this.addRequiredImports(sourceFile, targetModel, relationType, targetModel); sourceClass.formatText(); sourceFile.save(); } + addForeginKey(foreginKey, sourceModelPrimaryKeyType) { + let fkProperty = { + decorators: [{ name: 'property', arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"] }], + name: foreginKey, + type: sourceModelPrimaryKeyType, + }; + return fkProperty; + } + getHasMany(className, relationName) { let relationProperty = { - decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], + decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], name: relationName, type: className + '[]', }; @@ -163,7 +202,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getHasOne(className, relationName) { let relationProperty = { - decorators: [{name: 'hasOne', arguments: ['() => ' + className]}], + decorators: [{ name: 'hasOne', arguments: ['() => ' + className] }], name: relationName, type: className, }; @@ -173,8 +212,8 @@ module.exports = class ModelRelation extends ArtifactGenerator { getBelongsTo(className, relationName, fktype) { let relationProperty; relationProperty = { - decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], - name: relationName + 'Id', + decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], + name: relationName, type: fktype, }; return relationProperty; From 241877d3aa0efc0dc792d3191141b51b730efbae Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Mon, 11 Feb 2019 00:27:28 +0200 Subject: [PATCH 47/89] added a comment for additional work --- packages/cli/generators/relation/modelRelation.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index ea9559b1775f..4edbecea02c6 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -119,6 +119,8 @@ module.exports = class ModelRelation extends ArtifactGenerator { fktype, foreignKey ) { + // TOFO fix keyTppe and Foreignkey + // add keyTo when needed in both hasMany and belongsTo relation let sourceModelPrimaryKeyType = 'number' foreignKey = 'todoLId' let project = new ast.Project(); @@ -165,7 +167,6 @@ module.exports = class ModelRelation extends ArtifactGenerator { } break; case 'belongsTo': - //fix remvove ID this.isRelationExist(sourceClass, relationName) modelProperty = this.getBelongsTo( targetModel, From 668b7e9f0c9480eaf48269ba1bcc1ec984e79577 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 11 Feb 2019 08:17:13 -0500 Subject: [PATCH 48/89] Added prompt foreign key exist. --- packages/cli/generators/relation/index.js | 120 ++++++++++++++++++---- 1 file changed, 99 insertions(+), 21 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 1523ee883eea..bb72112c130a 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -21,11 +21,14 @@ const RepositoryRelation = require('./repositoryRelation'); const ModelRelation = require('./modelRelation'); const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; +const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; +const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE_SOURCE_MODEL = 'Please select source model'; const PROMPT_MESSAGE_TARGET_MODEL = 'Please select target model'; const PROMPT_MESSAGE_PROPERTY_NAME = 'Property name for the relation'; +const PROMPT_MESSAGE_FOREIGN_KEY_NAME = 'Foreign key name for the relation'; const relPathControllersFolder = '/controllers'; const relPathModelsFolder = '/models'; @@ -45,7 +48,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { * get the property name for the id field * @param {string} modelName */ - async _getModelIdProperty(modelName) { + async _getModelPrimaryKeyProperty(modelName) { let fileContent = ''; let modelFile = path.join( this.artifactInfo.modelDir, @@ -81,7 +84,22 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return fileName.getClassOrThrow(className); } - _calcForeignKeyType() { + async _calcSourceModelPrimaryKey() { + this.options.sourceModelPrimaryKey = await this._getModelPrimaryKeyProperty( + this.options.sourceModel, + ); + + if (this.options.sourceModelPrimaryKey === null) { + throw new Error("Source model primary key does not exist."); + } + } + + /** + * Read source model file and get type of the primary key. + * + * @return string + */ + _calcSourceModelPrimaryKeyType() { let project = new ast.Project(); const sourceFile = path.join( @@ -89,9 +107,23 @@ module.exports = class RelationGenerator extends ArtifactGenerator { utils.getModelFileName(this.artifactInfo.sourceModel.modelNameList), ); const sf = project.addExistingSourceFile(sourceFile); - this.options.foreignKeyType = this._getKeyType(sf, this.options.foreignKey); + this.options.sourceModelPrimaryKeyType = this._getKeyType( + sf, + this.options.sourceModelPrimaryKey + ); + } + + /** + * Generate default foreign key name. Foreign key name use in target model. + */ + _calcDefaultForeignKey() { + this.options.defaultForeignKeyName = + utils.camelCase(this.options.sourceModel) + + utils.toClassName(this.options.sourceModelPrimaryKey); } + + async _scaffold() { let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; @@ -110,21 +142,21 @@ module.exports = class RelationGenerator extends ArtifactGenerator { let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; - /* - switch (this.options.relationType) { - case relationUtils.relationType.belongsTo: - ctrl.generateControllerRelationBelongsTo(this.options); - break; - case relationUtils.relationType.hasMany: - ctrl.generateControllerRelationHasMany(this.options); - break; - case relationUtils.relationType.hasOne: - ctrl.generateControllerRelationHasOne(this.options); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - */ + + switch (this.options.relationType) { + case relationUtils.relationType.belongsTo: + ctrl.generateControllerRelationBelongsTo(this.options); + break; + case relationUtils.relationType.hasMany: + ctrl.generateControllerRelationHasMany(this.options); + break; + case relationUtils.relationType.hasOne: + ctrl.generateControllerRelationHasOne(this.options); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); @@ -272,14 +304,60 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } /** - * Prompt Foreign key if not exist: - * 1. From source model get primary key. If primary key does not exist error. + * Prompt foreign key if not exist: + * 1. From source model get primary key. If primary key does not exist - + * error. * 2. Get primary key type from source model. * 3. Generate foreign key (camelCase source class Name + primary key name). * 4. Check is foreign key exist in destination model. If not - prompt. * Error - if type is not the same. */ - async promptModelId() { + async promptForeignKey() { + if (this.shouldExit()) return false; + + if (_.isEmpty(this.options.sourceModel)) { + return this.exit(new Error(`${ERROR_NO_SOURCE_MODEL_SELECTED}`)); + } + + if (_.isEmpty(this.options.destinationModel)) { + return this.exit(new Error(`${ERROR_NO_DESTINATION_MODEL_SELECTED}`)); + } + + await this._calcSourceModelPrimaryKey(); + this._calcSourceModelPrimaryKeyType(); + this._calcDefaultForeignKey(); + + let project = new ast.Project(); + + const destinationFile = path.join( + this.artifactInfo.modelDir, + utils.getModelFileName(this.options.destinationModel), + ); + const df = project.addExistingSourceFile(destinationFile); + const cl = this._getClassObj(df); + this.options.destinationModelForeignKeyExist = + cl + .getProperties() + .map(x => x.getName()) + .includes(this.options.defaultForeignKeyName); + + if (!this.options.destinationModelForeignKeyExist) { + this.artifactInfo.destinationModelForeignKeyName = await this.prompt([ + { + type: 'string', + name: 'value', + message: PROMPT_MESSAGE_FOREIGN_KEY_NAME, + default: this.options.defaultForeignKeyName, + when: !this.artifactInfo.destinationModelForeignKeyName, + }, + ]); + this.options.destinationModelForeignKeyName = this.artifactInfo.destinationModelForeignKeyName; + } else { + this.options.destinationModelForeignKeyName = this.options.defaultForeignKeyName; + } + } + + async _promptModelId() { if (this.shouldExit()) return false; let idProperty; From 27000cab908ceeab1fcfb2b7f71e3ad8edea40fc Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 11 Feb 2019 08:18:26 -0500 Subject: [PATCH 49/89] Relation: removed unused function. --- packages/cli/generators/relation/index.js | 45 ----------------------- 1 file changed, 45 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index bb72112c130a..e4bad704479f 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -357,51 +357,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } } - async _promptModelId() { - if (this.shouldExit()) return false; - let idProperty; - - debug(`Model ID property name from command line: ${this.options.id}`); - debug(`Selected Models: ${this.artifactInfo.sourceModel}`); - - if (_.isEmpty(this.artifactInfo.sourceModel)) { - return this.exit(new Error(`${ERROR_NO_MODEL_SELECTED}`)); - } else { - const prompts = [ - { - type: 'input', - name: 'propertyName', - message: `Please enter the name of the ID property for ${ - this.artifactInfo.sourceModel - }:`, - default: 'id', - }, - ]; - - // user supplied the id from the command line - if (this.options.id) { - debug(`passing thru this.options.id with value : ${this.options.id}`); - - idProperty = this.options.id; - /** make sure it is only used once, in case user selected more - * than one model. - */ - delete this.options.id; - } else { - idProperty = await this._getModelIdProperty( - this.artifactInfo.sourceModel.modelNameList, - ); - - if (idProperty === null) { - const answer = await this.prompt(prompts); - idProperty = answer.propertyName; - } - } - this.options.foreignKey = idProperty; - this._calcForeignKeyType(); - } - } - async promptRelationName() { if (this.shouldExit()) return false; From 3fd09b864cbf419970702afcbf061baf367b8091 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Mon, 11 Feb 2019 15:31:13 +0200 Subject: [PATCH 50/89] create foreignKey in traget model if missing --- .../cli/generators/relation/modelRelation.js | 219 +++++++++--------- 1 file changed, 110 insertions(+), 109 deletions(-) diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 4edbecea02c6..7c70ec340f11 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -22,140 +22,57 @@ module.exports = class ModelRelation extends ArtifactGenerator { } generateRelationModel(options) { - let modelPath = this.artifactInfo.modelDir; - this.generateModel( - this.options.sourceModel, - this.options.destinationModel, - this.options.relationType, - modelPath, - this.options.foreignKey, - this.options.relationName, - this.options.foreignKeyType, - ); - } - - addFileToProject(project, path, modelName) { - const fileName = path + '/' + modelName + '.model.ts'; - return project.addExistingSourceFile(fileName); - } - - getClassesCount(fileName) { - return fileName.getClasses().length; - } - - getClassObj(fileName, modelName) { - return fileName.getClassOrThrow(modelName); - } - - isClassExist(fileName) { - return this.getClassesCount(fileName) == 1; - } - - getPropertiesCount(classObj) { - return classObj.getProperties().length; - } - - getPropertyStartPos(classObj) { - return classObj - .getChildSyntaxList() - .getChildAtIndex(this.getPropertiesCount(classObj) - 1) - .getPos(); - } - - getClassProperties(classObj) { - return classObj.getProperties(); - } + let path = this.artifactInfo.modelDir; + let sourceModel = this.options.sourceModel; + let targetModel = this.options.destinationModel; + let relationType = this.options.relationType; + let sourceModelPrimaryKey = this.options.foreignKey; + let relationName = this.options.relationName; + let fktype = this.options.foreignKeyType; - isPropertyExist(classObj, propertyName) { - return this.getClassProperties(classObj) - .map(x => x.getName()) - .includes(propertyName); - } - - isRelationExist(classObj, propertyName) { - if (this.isForeignKeyExist(classObj, propertyName)) { - console.log('property ' + propertyName + ' exsist in the model'); - throw new Error(' Property exsists'); - } - return - } - - - isForeignKeyExist(classObj, foreignKey) { - return this.isPropertyExist(classObj, foreignKey) - } - - getType(classObj, propertyName) { - return classObj - .getProperty(propertyName) - .getType() - .getText(); - } - - isDefaultForeignKey(classObj, sourceModelPrimaryKey, foreignKey) { - let defaultForeignKey = utils.camelCase(classObj.getName()) + utils.toClassName(sourceModelPrimaryKey) - if (defaultForeignKey === foreignKey) { - console.log('default foreignKey is missing in the target model'); - throw new Error(' missing foreginKey'); - } - return - } - - addPropertyToModel(classOBj, modelProperty) { - classOBj.insertProperty( - this.getPropertiesCount(classOBj), - modelProperty, - ); - classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); - } - - generateModel( - sourceModel, - targetModel, - relationType, - path, - sourceModelPrimaryKey, - relationName, - fktype, - foreignKey - ) { // TOFO fix keyTppe and Foreignkey // add keyTo when needed in both hasMany and belongsTo relation let sourceModelPrimaryKeyType = 'number' - foreignKey = 'todoLId' + let foreignKey = 'todoLId' + let project = new ast.Project(); + const sourceFile = this.addFileToProject( project, path, utils.kebabCase(sourceModel), ); - if (!this.isClassExist(sourceFile)) { - return; - } - const sourceClass = this.getClassObj(sourceFile, sourceModel); const targetFile = this.addFileToProject( project, path, utils.kebabCase(targetModel), ); + + const sourceClass = this.getClassObj(sourceFile, sourceModel); + + const targetClass = this.getClassObj(targetFile, targetModel); + + if (!this.isClassExist(sourceFile)) { + return; + } if (!this.isClassExist(targetFile)) { return; } - const targetClass = this.getClassObj(targetFile, targetModel); + let modelProperty; switch (relationType) { case 'hasMany': this.isRelationExist(sourceClass, relationName) - if (!(this.isForeignKeyExist(targetClass, foreignKey))) { - this.isDefaultForeignKey(sourceClass, sourceModelPrimaryKey, foreignKey) - modelProperty = this.addForeginKey(foreignKey, sourceModelPrimaryKeyType) - this.addPropertyToModel(targetClass, modelProperty); - targetClass.formatText(); - targetFile.save(); + if ((this.isForeignKeyExist(targetClass, foreignKey))) { + this.vlidateType(targetClass, foreignKey, sourceModelPrimaryKeyType) } + modelProperty = this.addForeginKey(foreignKey, sourceModelPrimaryKeyType) + this.addPropertyToModel(targetClass, modelProperty); + targetClass.formatText(); + targetFile.save(); modelProperty = this.getHasMany(targetModel, relationName); break; case 'hasOne': @@ -182,10 +99,94 @@ module.exports = class ModelRelation extends ArtifactGenerator { sourceFile.save(); } + addFileToProject(project, path, modelName) { + const fileName = path + '/' + modelName + '.model.ts'; + return project.addExistingSourceFile(fileName); + } + + addPropertyToModel(classOBj, modelProperty) { + classOBj.insertProperty( + this.getPropertiesCount(classOBj), + modelProperty, + ); + classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); + } + + + vlidateType(classObj, foriegnKeyName, foriegnKeyType) { + if ((classObj.getProperty(foriegnKeyName).getType().getText()) != foriegnKeyType) { + console.log(' foreignKey type has wrong Type '); + throw new Error('foreginKey Type Error'); + } + return + + } + isDefaultForeignKey(classObj, sourceModelPrimaryKey, foreignKey) { + let defaultForeignKey = utils.camelCase(classObj.getName()) + utils.toClassName(sourceModelPrimaryKey) + if (defaultForeignKey === foreignKey) { + console.log('default foreignKey is missing in the target model'); + throw new Error(' missing foreginKey'); + } + return + } + + isPropertyExist(classObj, propertyName) { + return this.getClassProperties(classObj) + .map(x => x.getName()) + .includes(propertyName); + } + + isRelationExist(classObj, propertyName) { + if (this.isForeignKeyExist(classObj, propertyName)) { + console.log('property ' + propertyName + ' exsist in the model'); + throw new Error(' Property exsists'); + } + return + } + + + isForeignKeyExist(classObj, foreignKey) { + return this.isPropertyExist(classObj, foreignKey) + } + + getType(classObj, propertyName) { + return classObj + .getProperty(propertyName) + .getType() + .getText(); + } + + getPropertiesCount(classObj) { + return classObj.getProperties().length; + } + + getPropertyStartPos(classObj) { + return classObj + .getChildSyntaxList() + .getChildAtIndex(this.getPropertiesCount(classObj) - 1) + .getPos(); + } + + getClassProperties(classObj) { + return classObj.getProperties(); + } + + getClassesCount(fileName) { + return fileName.getClasses().length; + } + + getClassObj(fileName, modelName) { + return fileName.getClassOrThrow(modelName); + } + + isClassExist(fileName) { + return this.getClassesCount(fileName) == 1; + } + addForeginKey(foreginKey, sourceModelPrimaryKeyType) { let fkProperty = { decorators: [{ name: 'property', arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"] }], - name: foreginKey, + name: foreginKey + '?', type: sourceModelPrimaryKeyType, }; return fkProperty; From 9fb6ac752654e4b490bb2a6fe7afdda4cd5623c2 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Mon, 11 Feb 2019 16:05:02 +0200 Subject: [PATCH 51/89] fixed hasMany Model --- packages/cli/generators/relation/index.js | 30 +++++++-------- .../cli/generators/relation/modelRelation.js | 37 ++++++------------- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index e4bad704479f..f7733080d167 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -142,21 +142,21 @@ module.exports = class RelationGenerator extends ArtifactGenerator { let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; - - switch (this.options.relationType) { - case relationUtils.relationType.belongsTo: - ctrl.generateControllerRelationBelongsTo(this.options); - break; - case relationUtils.relationType.hasMany: - ctrl.generateControllerRelationHasMany(this.options); - break; - case relationUtils.relationType.hasOne: - ctrl.generateControllerRelationHasOne(this.options); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - + /* + switch (this.options.relationType) { + case relationUtils.relationType.belongsTo: + ctrl.generateControllerRelationBelongsTo(this.options); + break; + case relationUtils.relationType.hasMany: + ctrl.generateControllerRelationHasMany(this.options); + break; + case relationUtils.relationType.hasOne: + ctrl.generateControllerRelationHasOne(this.options); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + */ //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 7c70ec340f11..4a0d84b8e609 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -26,14 +26,13 @@ module.exports = class ModelRelation extends ArtifactGenerator { let sourceModel = this.options.sourceModel; let targetModel = this.options.destinationModel; let relationType = this.options.relationType; - let sourceModelPrimaryKey = this.options.foreignKey; + let sourceModelPrimaryKey = this.options.sourceModelPrimaryKey; let relationName = this.options.relationName; - let fktype = this.options.foreignKeyType; + let fktype = this.options.sourceModelPrimaryKeyType; + let foreignKey = this.options.defaultForeignKeyName; + let isForeignKeyExist = this.options.destinationModelForeignKeyExist; - // TOFO fix keyTppe and Foreignkey // add keyTo when needed in both hasMany and belongsTo relation - let sourceModelPrimaryKeyType = 'number' - let foreignKey = 'todoLId' let project = new ast.Project(); @@ -66,13 +65,14 @@ module.exports = class ModelRelation extends ArtifactGenerator { switch (relationType) { case 'hasMany': this.isRelationExist(sourceClass, relationName) - if ((this.isForeignKeyExist(targetClass, foreignKey))) { - this.vlidateType(targetClass, foreignKey, sourceModelPrimaryKeyType) + if (isForeignKeyExist) { + this.vlidateType(targetClass, foreignKey, fktype) + } else { + modelProperty = this.addForeginKey(foreignKey, fktype) + this.addPropertyToModel(targetClass, modelProperty); + targetClass.formatText(); + targetFile.save(); } - modelProperty = this.addForeginKey(foreignKey, sourceModelPrimaryKeyType) - this.addPropertyToModel(targetClass, modelProperty); - targetClass.formatText(); - targetFile.save(); modelProperty = this.getHasMany(targetModel, relationName); break; case 'hasOne': @@ -121,14 +121,6 @@ module.exports = class ModelRelation extends ArtifactGenerator { return } - isDefaultForeignKey(classObj, sourceModelPrimaryKey, foreignKey) { - let defaultForeignKey = utils.camelCase(classObj.getName()) + utils.toClassName(sourceModelPrimaryKey) - if (defaultForeignKey === foreignKey) { - console.log('default foreignKey is missing in the target model'); - throw new Error(' missing foreginKey'); - } - return - } isPropertyExist(classObj, propertyName) { return this.getClassProperties(classObj) @@ -137,18 +129,13 @@ module.exports = class ModelRelation extends ArtifactGenerator { } isRelationExist(classObj, propertyName) { - if (this.isForeignKeyExist(classObj, propertyName)) { + if (this.isPropertyExist(classObj, propertyName)) { console.log('property ' + propertyName + ' exsist in the model'); throw new Error(' Property exsists'); } return } - - isForeignKeyExist(classObj, foreignKey) { - return this.isPropertyExist(classObj, foreignKey) - } - getType(classObj, propertyName) { return classObj .getProperty(propertyName) From 88a642601f08ff5929018e56963a21d7a543d23b Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 11 Feb 2019 10:23:44 -0500 Subject: [PATCH 52/89] Fixed relation controller templates. --- .../generators/relation/controllerRelation.js | 8 ++-- packages/cli/generators/relation/index.js | 37 +++++++++++-------- .../cli/generators/relation/modelRelation.js | 4 +- ...troller-relation-template-belongsto.ts.ejs | 2 +- ...ntroller-relation-template-has-many.ts.ejs | 8 ++-- 5 files changed, 32 insertions(+), 27 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 39f7f04dd7c8..55a1f1413c9f 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -83,8 +83,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { ); this.artifactInfo.relationPropertyName = options.relationName; - this.artifactInfo.foreignKey = options.foreignKey; - this.artifactInfo.foreignKeyType = options.foreignKeyType; + this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; + this.artifactInfo.sourceModelPrimaryKeyType = options.sourceModelPrimaryKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); @@ -119,8 +119,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { options.destinationModel, ); this.artifactInfo.relationPropertyName = options.destinationModel; - this.artifactInfo.foreignKey = options.foreignKey; - this.artifactInfo.foreignKeyType = options.foreignKeyType; + this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; + this.artifactInfo.sourceModelPrimaryKeyType = options.sourceModelPrimaryKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index f7733080d167..73cedcbf6c17 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -142,21 +142,21 @@ module.exports = class RelationGenerator extends ArtifactGenerator { let ctrl = new ControllerRelation(this.args, this.opts); this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; - /* - switch (this.options.relationType) { - case relationUtils.relationType.belongsTo: - ctrl.generateControllerRelationBelongsTo(this.options); - break; - case relationUtils.relationType.hasMany: - ctrl.generateControllerRelationHasMany(this.options); - break; - case relationUtils.relationType.hasOne: - ctrl.generateControllerRelationHasOne(this.options); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - */ + + switch (this.options.relationType) { + case relationUtils.relationType.belongsTo: + ctrl.generateControllerRelationBelongsTo(this.options); + break; + case relationUtils.relationType.hasMany: + ctrl.generateControllerRelationHasMany(this.options); + break; + case relationUtils.relationType.hasOne: + ctrl.generateControllerRelationHasOne(this.options); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); @@ -194,7 +194,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { case relationUtils.relationType.belongsTo: defaultRelationName = utils.camelCase(this.options.destinationModel) + - utils.toClassName(this.options.foreignKey); + utils.toClassName(this.options.sourceModelPrimaryKey); break; case relationUtils.relationType.hasMany: defaultRelationName = utils.pluralize( @@ -315,6 +315,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { async promptForeignKey() { if (this.shouldExit()) return false; + + if (_.isEmpty(this.options.sourceModel)) { return this.exit(new Error(`${ERROR_NO_SOURCE_MODEL_SELECTED}`)); } @@ -327,6 +329,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this._calcSourceModelPrimaryKeyType(); this._calcDefaultForeignKey(); + if (this.options.relationType === relationUtils.relationType.belongsTo) { + return; + } let project = new ast.Project(); const destinationFile = path.join( diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 4a0d84b8e609..882b4d6b4133 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -88,7 +88,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { modelProperty = this.getBelongsTo( targetModel, relationName, - utils.toClassName(sourceModelPrimaryKeyType), + utils.toClassName(fktype), ) break; } @@ -114,7 +114,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { vlidateType(classObj, foriegnKeyName, foriegnKeyType) { - if ((classObj.getProperty(foriegnKeyName).getType().getText()) != foriegnKeyType) { + if (utils.lowerCase(classObj.getProperty(foriegnKeyName).getType().getText()) != foriegnKeyType) { console.log(' foreignKey type has wrong Type '); throw new Error('foreginKey Type Error'); } diff --git a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs index 17583f9e0de7..09cd9a1932e2 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs @@ -16,7 +16,7 @@ export class <%= controllerClassName %> { @get('/<%= sourceModelName %>s/{id}/<%= targetModelName %>') async get<%= targetModelClassName %>( - @param.path.<%= foreignKeyType %>('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, + @param.path.<%= sourceModelPrimaryKeyType %>('id') <%= relationPropertyName %>: typeof <%= sourceModelClassName %>.prototype.<%= sourceModelPrimaryKey %>, ): Promise<<%= targetModelClassName %>> { return await this.<%= paramSourceRepository %>.<%= paramTargetModel %>(<%= relationPropertyName %>); } diff --git a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs index 4d04f8fee5f2..c7e0df2976cc 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-has-many.ts.ejs @@ -35,7 +35,7 @@ export class <%= controllerClassName %> { }, }) async find( - @param.path.<%= foreignKeyType %>('id') id: <%= foreignKeyType %>, + @param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>, @param.query.object('filter') filter?: Filter, ): Promise<<%= targetModelClassName %>[]> { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).find(filter); @@ -50,7 +50,7 @@ export class <%= controllerClassName %> { }, }) async create( - @param.path.<%= foreignKeyType %>('id') id: typeof <%= sourceModelClassName %>.prototype.<%= foreignKey %>, + @param.path.<%= sourceModelPrimaryKeyType %>('id') id: typeof <%= sourceModelClassName %>.prototype.<%= sourceModelPrimaryKey %>, @requestBody() <%= targetModelName %>: <%= targetModelClassName %>, ): Promise<<%= targetModelClassName %>> { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).create(<%= targetModelName %>); @@ -65,7 +65,7 @@ export class <%= controllerClassName %> { }, }) async patch( - @param.path.<%= foreignKeyType %>('id') id: <%= foreignKeyType %>, + @param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>, @requestBody() <%= targetModelName %>: Partial<<%= targetModelClassName %>>, @param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where, ): Promise { @@ -81,7 +81,7 @@ export class <%= controllerClassName %> { }, }) async delete( - @param.path.<%= foreignKeyType %>('id') id: <%= foreignKeyType %>, + @param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>, @param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where, ): Promise { return await this.<%= paramSourceRepository %>.<%= targetModelName %>s(id).delete(where); From 2809baa8fe6952fbe0fb1bfada3585f0242d168d Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 11 Feb 2019 11:05:47 -0500 Subject: [PATCH 53/89] Running lint:fix. --- .../generators/relation/controllerRelation.js | 6 ++- packages/cli/generators/relation/index.js | 21 ++++----- .../cli/generators/relation/modelRelation.js | 46 +++++++++++-------- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js index 55a1f1413c9f..c329cbfbbf94 100644 --- a/packages/cli/generators/relation/controllerRelation.js +++ b/packages/cli/generators/relation/controllerRelation.js @@ -84,7 +84,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { this.artifactInfo.relationPropertyName = options.relationName; this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; - this.artifactInfo.sourceModelPrimaryKeyType = options.sourceModelPrimaryKeyType; + this.artifactInfo.sourceModelPrimaryKeyType = + options.sourceModelPrimaryKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); @@ -120,7 +121,8 @@ module.exports = class ControllerRelation extends ArtifactGenerator { ); this.artifactInfo.relationPropertyName = options.destinationModel; this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; - this.artifactInfo.sourceModelPrimaryKeyType = options.sourceModelPrimaryKeyType; + this.artifactInfo.sourceModelPrimaryKeyType = + options.sourceModelPrimaryKeyType; const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 73cedcbf6c17..a77840cf13a3 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -90,7 +90,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); if (this.options.sourceModelPrimaryKey === null) { - throw new Error("Source model primary key does not exist."); + throw new Error('Source model primary key does not exist.'); } } @@ -109,7 +109,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { const sf = project.addExistingSourceFile(sourceFile); this.options.sourceModelPrimaryKeyType = this._getKeyType( sf, - this.options.sourceModelPrimaryKey + this.options.sourceModelPrimaryKey, ); } @@ -122,8 +122,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { utils.toClassName(this.options.sourceModelPrimaryKey); } - - async _scaffold() { let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; @@ -247,8 +245,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -315,8 +313,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { async promptForeignKey() { if (this.shouldExit()) return false; - - if (_.isEmpty(this.options.sourceModel)) { return this.exit(new Error(`${ERROR_NO_SOURCE_MODEL_SELECTED}`)); } @@ -340,11 +336,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); const df = project.addExistingSourceFile(destinationFile); const cl = this._getClassObj(df); - this.options.destinationModelForeignKeyExist = - cl - .getProperties() - .map(x => x.getName()) - .includes(this.options.defaultForeignKeyName); + this.options.destinationModelForeignKeyExist = cl + .getProperties() + .map(x => x.getName()) + .includes(this.options.defaultForeignKeyName); if (!this.options.destinationModelForeignKeyExist) { this.artifactInfo.destinationModelForeignKeyName = await this.prompt([ diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js index 882b4d6b4133..ca6542cf36a5 100644 --- a/packages/cli/generators/relation/modelRelation.js +++ b/packages/cli/generators/relation/modelRelation.js @@ -36,7 +36,6 @@ module.exports = class ModelRelation extends ArtifactGenerator { let project = new ast.Project(); - const sourceFile = this.addFileToProject( project, path, @@ -64,11 +63,11 @@ module.exports = class ModelRelation extends ArtifactGenerator { switch (relationType) { case 'hasMany': - this.isRelationExist(sourceClass, relationName) + this.isRelationExist(sourceClass, relationName); if (isForeignKeyExist) { - this.vlidateType(targetClass, foreignKey, fktype) + this.vlidateType(targetClass, foreignKey, fktype); } else { - modelProperty = this.addForeginKey(foreignKey, fktype) + modelProperty = this.addForeginKey(foreignKey, fktype); this.addPropertyToModel(targetClass, modelProperty); targetClass.formatText(); targetFile.save(); @@ -84,16 +83,16 @@ module.exports = class ModelRelation extends ArtifactGenerator { } break; case 'belongsTo': - this.isRelationExist(sourceClass, relationName) + this.isRelationExist(sourceClass, relationName); modelProperty = this.getBelongsTo( targetModel, relationName, utils.toClassName(fktype), - ) + ); break; } - this.addPropertyToModel(sourceClass, modelProperty) + this.addPropertyToModel(sourceClass, modelProperty); this.addRequiredImports(sourceFile, targetModel, relationType, targetModel); sourceClass.formatText(); sourceFile.save(); @@ -105,21 +104,23 @@ module.exports = class ModelRelation extends ArtifactGenerator { } addPropertyToModel(classOBj, modelProperty) { - classOBj.insertProperty( - this.getPropertiesCount(classOBj), - modelProperty, - ); + classOBj.insertProperty(this.getPropertiesCount(classOBj), modelProperty); classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); } - vlidateType(classObj, foriegnKeyName, foriegnKeyType) { - if (utils.lowerCase(classObj.getProperty(foriegnKeyName).getType().getText()) != foriegnKeyType) { + if ( + utils.lowerCase( + classObj + .getProperty(foriegnKeyName) + .getType() + .getText(), + ) != foriegnKeyType + ) { console.log(' foreignKey type has wrong Type '); throw new Error('foreginKey Type Error'); } - return - + return; } isPropertyExist(classObj, propertyName) { @@ -133,7 +134,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { console.log('property ' + propertyName + ' exsist in the model'); throw new Error(' Property exsists'); } - return + return; } getType(classObj, propertyName) { @@ -172,7 +173,12 @@ module.exports = class ModelRelation extends ArtifactGenerator { addForeginKey(foreginKey, sourceModelPrimaryKeyType) { let fkProperty = { - decorators: [{ name: 'property', arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"] }], + decorators: [ + { + name: 'property', + arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"], + }, + ], name: foreginKey + '?', type: sourceModelPrimaryKeyType, }; @@ -181,7 +187,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getHasMany(className, relationName) { let relationProperty = { - decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], + decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], name: relationName, type: className + '[]', }; @@ -191,7 +197,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getHasOne(className, relationName) { let relationProperty = { - decorators: [{ name: 'hasOne', arguments: ['() => ' + className] }], + decorators: [{name: 'hasOne', arguments: ['() => ' + className]}], name: relationName, type: className, }; @@ -201,7 +207,7 @@ module.exports = class ModelRelation extends ArtifactGenerator { getBelongsTo(className, relationName, fktype) { let relationProperty; relationProperty = { - decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], + decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], name: relationName, type: fktype, }; From 0d2112b8fd1bd91059676790ce2271ee6bdafd30 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 12 Feb 2019 08:37:36 -0500 Subject: [PATCH 54/89] ControllerRelation class splited to separated classes per relation type. --- .../generators/relation/controllerRelation.js | 153 ------------------ packages/cli/generators/relation/index.js | 21 ++- packages/cli/generators/relation/relation.js | 57 +++++++ .../generators/relation/relationBelongsTo.js | 64 ++++++++ .../generators/relation/relationHasMany.js | 59 +++++++ .../cli/generators/relation/relationHasOne.js | 21 +++ ...oller-relation-template-belongs-to.ts.ejs} | 0 7 files changed, 215 insertions(+), 160 deletions(-) delete mode 100644 packages/cli/generators/relation/controllerRelation.js create mode 100644 packages/cli/generators/relation/relation.js create mode 100644 packages/cli/generators/relation/relationBelongsTo.js create mode 100644 packages/cli/generators/relation/relationHasMany.js create mode 100644 packages/cli/generators/relation/relationHasOne.js rename packages/cli/generators/relation/templates/{controller-relation-template-belongsto.ts.ejs => controller-relation-template-belongs-to.ts.ejs} (100%) diff --git a/packages/cli/generators/relation/controllerRelation.js b/packages/cli/generators/relation/controllerRelation.js deleted file mode 100644 index c329cbfbbf94..000000000000 --- a/packages/cli/generators/relation/controllerRelation.js +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright IBM Corp. 2017,2018. All Rights Reserved. -// Node module: @loopback/cli -// This file is licensed under the MIT License. -// License text available at https://opensource.org/licenses/MIT -// Author: Raphael Drai at r.drai@f5.com - -'use strict'; - -const _ = require('lodash'); -const ArtifactGenerator = require('../../lib/artifact-generator'); -const path = require('path'); -const utils = require('../../lib/utils'); - -const CONTROLLER_TEMPLATE_PATH_HAS_MANY = - 'controller-relation-template-has-many.ts.ejs'; -const CONTROLLER_TEMPLATE_PATH_HAS_ONE = - 'controller-relation-template-has-one.ts.ejs'; -const CONTROLLER_TEMPLATE_PATH_BELONGS_TO = - 'controller-relation-template-belongsto.ts.ejs'; - -// Exportable constants -module.exports = class ControllerRelation extends ArtifactGenerator { - // Note: arguments and options should be defined in the constructor. - constructor(args, opts) { - super(args, opts); - } - - _setupGenerator() { - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - this.artifactInfo.outDir = path.resolve( - this.artifactInfo.rootDir, - 'controllers', - ); - this.artifactInfo.modelDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - this.artifactInfo.repositoryDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories', - ); - - this.option('controllerType', { - type: String, - required: false, - description: 'Type for the ' + this.artifactInfo.type, - }); - } - - scaffold() { - // We don't want to call the base scaffold function since it copies - // all of the templates! - // we can set here additional specific this.artifactInfo.xxx parameters if needed - - return; - } - - generateControllerRelationBelongsTo(options) { - this.artifactInfo.sourceModelClassName = options.sourceModel; - this.artifactInfo.targetModelClassName = options.destinationModel; - this.artifactInfo.paramTargetModel = utils.camelCase( - options.destinationModel, - ); - this.artifactInfo.sourceRepositoryClassName = - this.artifactInfo.sourceModelClassName + 'Repository'; - this.artifactInfo.controllerClassName = - this.artifactInfo.sourceModelClassName + - this.artifactInfo.targetModelClassName + - 'Controller'; - - this.artifactInfo.paramSourceRepository = utils.camelCase( - this.artifactInfo.sourceModelClassName + 'Repository', - ); - - this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); - this.artifactInfo.targetModelName = utils.kebabCase( - options.destinationModel, - ); - - this.artifactInfo.relationPropertyName = options.relationName; - this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; - this.artifactInfo.sourceModelPrimaryKeyType = - options.sourceModelPrimaryKeyType; - - const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); - - this.artifactInfo.name = - options.sourceModel + '-' + options.destinationModel; - this.artifactInfo.outFile = - utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; - - const dest = this.destinationPath( - path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), - ); - - this.copyTemplatedFiles(source, dest, this.artifactInfo); - return; - } - - generateControllerRelationHasMany(options) { - this.artifactInfo.sourceModelClassName = options.sourceModel; - this.artifactInfo.targetModelClassName = options.destinationModel; - this.artifactInfo.sourceRepositoryClassName = - this.artifactInfo.sourceModelClassName + 'Repository'; - this.artifactInfo.controllerClassName = - this.artifactInfo.sourceModelClassName + - this.artifactInfo.targetModelClassName + - 'Controller'; - this.artifactInfo.paramSourceRepository = utils.camelCase( - this.artifactInfo.sourceModelClassName + 'Repository', - ); - - this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); - this.artifactInfo.targetModelName = utils.kebabCase( - options.destinationModel, - ); - this.artifactInfo.relationPropertyName = options.destinationModel; - this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; - this.artifactInfo.sourceModelPrimaryKeyType = - options.sourceModelPrimaryKeyType; - - const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); - - this.artifactInfo.name = - options.sourceModel + '-' + options.destinationModel; - this.artifactInfo.outFile = - utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; - - const dest = this.destinationPath( - path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), - ); - - this.copyTemplatedFiles(source, dest, this.artifactInfo); - return; - } - - generateControllerRelationHasOne(options) { - throw new Error('Not implemented'); - } - - addFileToProject(project, fileName) { - try { - return project.addExistingSourceFile(fileName); - } catch (e) { - throw new Error("source model file: '" + fileName + "' is not found."); - } - } -}; diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index a77840cf13a3..879b594ea817 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -16,7 +16,10 @@ const tsquery = require('../../lib/ast-helper'); const ast = require('ts-simple-ast'); const relationUtils = require('./relationutils'); -const ControllerRelation = require('./controllerRelation'); +const RelationBelongsTo = require('./relationBelongsTo'); +const RelationHasMany = require('./relationHasMany'); +const RelationHasOne = require('./relationHasOne'); + const RepositoryRelation = require('./repositoryRelation'); const ModelRelation = require('./modelRelation'); @@ -137,19 +140,23 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } debug('Invoke Controller generator...'); - let ctrl = new ControllerRelation(this.args, this.opts); + var relation; + this.artifactInfo.name = this.options.relationType; this.artifactInfo.relPath = relPathCtrl; switch (this.options.relationType) { case relationUtils.relationType.belongsTo: - ctrl.generateControllerRelationBelongsTo(this.options); + relation = new RelationBelongsTo(this.args, this.opts); + relation.generateControllers(this.options); break; case relationUtils.relationType.hasMany: - ctrl.generateControllerRelationHasMany(this.options); + relation = new RelationHasMany(this.args, this.opts); + relation.generateControllers(this.options); break; case relationUtils.relationType.hasOne: - ctrl.generateControllerRelationHasOne(this.options); + relation = new RelationHasOne(this.args, this.opts); + relation.generateControllers(this.options); break; default: throw new Error(ERROR_INCORRECT_RELATION_TYPE); @@ -245,8 +252,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } diff --git a/packages/cli/generators/relation/relation.js b/packages/cli/generators/relation/relation.js new file mode 100644 index 000000000000..6464c4f527e9 --- /dev/null +++ b/packages/cli/generators/relation/relation.js @@ -0,0 +1,57 @@ +'use strict'; + +const ArtifactGenerator = require('../../lib/artifact-generator'); +const path = require('path'); +const utils = require('../../lib/utils'); + +module.exports = class RelationGenerator extends ArtifactGenerator { + constructor(args, opts) { + super(args, opts); + } + + _setupGenerator() { + super._setupGenerator(); + this.artifactInfo = { + type: 'relation', + rootDir: utils.sourceRootDir, + }; + + this.artifactInfo.outDir = path.resolve( + this.artifactInfo.rootDir, + 'controllers', + ); + this.artifactInfo.modelDir = path.resolve( + this.artifactInfo.rootDir, + 'models', + ); + this.artifactInfo.repositoryDir = path.resolve( + this.artifactInfo.rootDir, + 'repositories', + ); + + this.option('controllerType', { + type: String, + required: false, + description: 'Type for the ' + this.artifactInfo.type, + }); + } + + scaffold() { + // We don't want to call the base scaffold function since it copies + // all of the templates! + // we can set here additional specific this.artifactInfo.xxx parameters if needed + + return; + } + generateControllers(options) { + throw new Error('Not implemented'); + } + + generateModels(options) { + throw new Error('Not implemented'); + } + + generateRepositories(options) { + throw new Error('Not implemented'); + } +}; diff --git a/packages/cli/generators/relation/relationBelongsTo.js b/packages/cli/generators/relation/relationBelongsTo.js new file mode 100644 index 000000000000..e2bf87e69851 --- /dev/null +++ b/packages/cli/generators/relation/relationBelongsTo.js @@ -0,0 +1,64 @@ +'use strict'; + +const path = require('path'); +const RelationGenerator = require('./relation'); +const utils = require('../../lib/utils'); + +const CONTROLLER_TEMPLATE_PATH_BELONGS_TO = + 'controller-relation-template-belongs-to.ts.ejs'; + +module.exports = class RelationBelongsTo extends RelationGenerator { + constructor(args, opts) { + super(args, opts); + } + + generateControllers(options) { + this.artifactInfo.sourceModelClassName = options.sourceModel; + this.artifactInfo.targetModelClassName = options.destinationModel; + this.artifactInfo.paramTargetModel = utils.camelCase( + options.destinationModel, + ); + this.artifactInfo.sourceRepositoryClassName = + this.artifactInfo.sourceModelClassName + 'Repository'; + this.artifactInfo.controllerClassName = + this.artifactInfo.sourceModelClassName + + this.artifactInfo.targetModelClassName + + 'Controller'; + + this.artifactInfo.paramSourceRepository = utils.camelCase( + this.artifactInfo.sourceModelClassName + 'Repository', + ); + + this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); + this.artifactInfo.targetModelName = utils.kebabCase( + options.destinationModel, + ); + + this.artifactInfo.relationPropertyName = options.relationName; + this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; + this.artifactInfo.sourceModelPrimaryKeyType = + options.sourceModelPrimaryKeyType; + + const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_BELONGS_TO); + + this.artifactInfo.name = + options.sourceModel + '-' + options.destinationModel; + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), + ); + + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; + } + + generateModels(options) { + throw new Error('Not implemented'); + } + + generateRepositories(options) { + throw new Error('Not implemented'); + } +}; diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js new file mode 100644 index 000000000000..ed865543df0b --- /dev/null +++ b/packages/cli/generators/relation/relationHasMany.js @@ -0,0 +1,59 @@ +'use strict'; + +const path = require('path'); +const RelationGenerator = require('./relation'); +const utils = require('../../lib/utils'); + +const CONTROLLER_TEMPLATE_PATH_HAS_MANY = + 'controller-relation-template-has-many.ts.ejs'; + +module.exports = class RelationHasMany extends RelationGenerator { + constructor(args, opts) { + super(args, opts); + } + + generateControllers(options) { + this.artifactInfo.sourceModelClassName = options.sourceModel; + this.artifactInfo.targetModelClassName = options.destinationModel; + this.artifactInfo.sourceRepositoryClassName = + this.artifactInfo.sourceModelClassName + 'Repository'; + this.artifactInfo.controllerClassName = + this.artifactInfo.sourceModelClassName + + this.artifactInfo.targetModelClassName + + 'Controller'; + this.artifactInfo.paramSourceRepository = utils.camelCase( + this.artifactInfo.sourceModelClassName + 'Repository', + ); + + this.artifactInfo.sourceModelName = utils.kebabCase(options.sourceModel); + this.artifactInfo.targetModelName = utils.kebabCase( + options.destinationModel, + ); + this.artifactInfo.relationPropertyName = options.destinationModel; + this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey; + this.artifactInfo.sourceModelPrimaryKeyType = + options.sourceModelPrimaryKeyType; + + const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_MANY); + + this.artifactInfo.name = + options.sourceModel + '-' + options.destinationModel; + this.artifactInfo.outFile = + utils.kebabCase(this.artifactInfo.name) + '.controller.ts'; + + const dest = this.destinationPath( + path.join(this.artifactInfo.outDir, this.artifactInfo.outFile), + ); + + this.copyTemplatedFiles(source, dest, this.artifactInfo); + return; + } + + generateModels(options) { + throw new Error('Not implemented'); + } + + generateRepositories(options) { + throw new Error('Not implemented'); + } +}; diff --git a/packages/cli/generators/relation/relationHasOne.js b/packages/cli/generators/relation/relationHasOne.js new file mode 100644 index 000000000000..0de64223378f --- /dev/null +++ b/packages/cli/generators/relation/relationHasOne.js @@ -0,0 +1,21 @@ +'use strict'; + +const RelationGenerator = require('./relation'); + +module.exports = class RelationHasOne extends RelationGenerator { + constructor(args, opts) { + super(args, opts); + } + + generateControllers(options) { + throw new Error('Not implemented'); + } + + generateModels(options) { + throw new Error('Not implemented'); + } + + generateRepositories(options) { + throw new Error('Not implemented'); + } +}; diff --git a/packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs similarity index 100% rename from packages/cli/generators/relation/templates/controller-relation-template-belongsto.ts.ejs rename to packages/cli/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs From 786da3cfb69169f8bdb7b46744247e2e64c3e500 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 12 Feb 2019 08:52:23 -0500 Subject: [PATCH 55/89] relation.generateControllers call moved from switch. --- packages/cli/generators/relation/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 879b594ea817..fe4c3b064f4d 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -148,20 +148,19 @@ module.exports = class RelationGenerator extends ArtifactGenerator { switch (this.options.relationType) { case relationUtils.relationType.belongsTo: relation = new RelationBelongsTo(this.args, this.opts); - relation.generateControllers(this.options); break; case relationUtils.relationType.hasMany: relation = new RelationHasMany(this.args, this.opts); - relation.generateControllers(this.options); break; case relationUtils.relationType.hasOne: relation = new RelationHasOne(this.args, this.opts); - relation.generateControllers(this.options); break; default: throw new Error(ERROR_INCORRECT_RELATION_TYPE); } + relation.generateControllers(this.options); + //Invoke here Model and Repository Generators debug('Invoke Model generator...'); let model = new ModelRelation(this.args, this.opts); From 22f981bce330e049b6b587421e5d463d72f65c0f Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 12 Feb 2019 09:57:02 -0500 Subject: [PATCH 56/89] ModelRelation class splited to diff classes. --- packages/cli/generators/relation/index.js | 5 +- .../cli/generators/relation/modelRelation.js | 283 ------------------ .../generators/relation/relationBelongsTo.js | 62 +++- .../generators/relation/relationHasMany.js | 68 ++++- .../cli/generators/relation/relationutils.js | 149 ++++++++- 5 files changed, 277 insertions(+), 290 deletions(-) delete mode 100644 packages/cli/generators/relation/modelRelation.js diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index fe4c3b064f4d..5a5a37474694 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -21,7 +21,6 @@ const RelationHasMany = require('./relationHasMany'); const RelationHasOne = require('./relationHasOne'); const RepositoryRelation = require('./repositoryRelation'); -const ModelRelation = require('./modelRelation'); const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; @@ -161,11 +160,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { relation.generateControllers(this.options); - //Invoke here Model and Repository Generators debug('Invoke Model generator...'); - let model = new ModelRelation(this.args, this.opts); this.artifactInfo.relPath = relPathModel; - model.generateRelationModel(this.options); + relation.generateModels(this.options); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); diff --git a/packages/cli/generators/relation/modelRelation.js b/packages/cli/generators/relation/modelRelation.js deleted file mode 100644 index ca6542cf36a5..000000000000 --- a/packages/cli/generators/relation/modelRelation.js +++ /dev/null @@ -1,283 +0,0 @@ -'use strict'; -const ArtifactGenerator = require('../../lib/artifact-generator'); -const debug = require('../../lib/debug')('relation-generator'); -const inspect = require('util').inspect; -const path = require('path'); -const chalk = require('chalk'); -const utils = require('../../lib/utils'); -const ast = require('ts-simple-ast'); - -module.exports = class ModelRelation extends ArtifactGenerator { - _setupGenerator() { - super._setupGenerator(); - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - - this.artifactInfo.modelDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - } - - generateRelationModel(options) { - let path = this.artifactInfo.modelDir; - let sourceModel = this.options.sourceModel; - let targetModel = this.options.destinationModel; - let relationType = this.options.relationType; - let sourceModelPrimaryKey = this.options.sourceModelPrimaryKey; - let relationName = this.options.relationName; - let fktype = this.options.sourceModelPrimaryKeyType; - let foreignKey = this.options.defaultForeignKeyName; - let isForeignKeyExist = this.options.destinationModelForeignKeyExist; - - // add keyTo when needed in both hasMany and belongsTo relation - - let project = new ast.Project(); - - const sourceFile = this.addFileToProject( - project, - path, - utils.kebabCase(sourceModel), - ); - - const targetFile = this.addFileToProject( - project, - path, - utils.kebabCase(targetModel), - ); - - const sourceClass = this.getClassObj(sourceFile, sourceModel); - - const targetClass = this.getClassObj(targetFile, targetModel); - - if (!this.isClassExist(sourceFile)) { - return; - } - if (!this.isClassExist(targetFile)) { - return; - } - - let modelProperty; - - switch (relationType) { - case 'hasMany': - this.isRelationExist(sourceClass, relationName); - if (isForeignKeyExist) { - this.vlidateType(targetClass, foreignKey, fktype); - } else { - modelProperty = this.addForeginKey(foreignKey, fktype); - this.addPropertyToModel(targetClass, modelProperty); - targetClass.formatText(); - targetFile.save(); - } - modelProperty = this.getHasMany(targetModel, relationName); - break; - case 'hasOne': - if (this.isPropertyExist(sourceClass, relationName)) { - console.log('property ' + relationName + ' exsist in the model'); - throw new Error(' Property exsists'); - } else { - modelProperty = this.getHasOne(targetModel, relationName); - } - break; - case 'belongsTo': - this.isRelationExist(sourceClass, relationName); - modelProperty = this.getBelongsTo( - targetModel, - relationName, - utils.toClassName(fktype), - ); - break; - } - - this.addPropertyToModel(sourceClass, modelProperty); - this.addRequiredImports(sourceFile, targetModel, relationType, targetModel); - sourceClass.formatText(); - sourceFile.save(); - } - - addFileToProject(project, path, modelName) { - const fileName = path + '/' + modelName + '.model.ts'; - return project.addExistingSourceFile(fileName); - } - - addPropertyToModel(classOBj, modelProperty) { - classOBj.insertProperty(this.getPropertiesCount(classOBj), modelProperty); - classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); - } - - vlidateType(classObj, foriegnKeyName, foriegnKeyType) { - if ( - utils.lowerCase( - classObj - .getProperty(foriegnKeyName) - .getType() - .getText(), - ) != foriegnKeyType - ) { - console.log(' foreignKey type has wrong Type '); - throw new Error('foreginKey Type Error'); - } - return; - } - - isPropertyExist(classObj, propertyName) { - return this.getClassProperties(classObj) - .map(x => x.getName()) - .includes(propertyName); - } - - isRelationExist(classObj, propertyName) { - if (this.isPropertyExist(classObj, propertyName)) { - console.log('property ' + propertyName + ' exsist in the model'); - throw new Error(' Property exsists'); - } - return; - } - - getType(classObj, propertyName) { - return classObj - .getProperty(propertyName) - .getType() - .getText(); - } - - getPropertiesCount(classObj) { - return classObj.getProperties().length; - } - - getPropertyStartPos(classObj) { - return classObj - .getChildSyntaxList() - .getChildAtIndex(this.getPropertiesCount(classObj) - 1) - .getPos(); - } - - getClassProperties(classObj) { - return classObj.getProperties(); - } - - getClassesCount(fileName) { - return fileName.getClasses().length; - } - - getClassObj(fileName, modelName) { - return fileName.getClassOrThrow(modelName); - } - - isClassExist(fileName) { - return this.getClassesCount(fileName) == 1; - } - - addForeginKey(foreginKey, sourceModelPrimaryKeyType) { - let fkProperty = { - decorators: [ - { - name: 'property', - arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"], - }, - ], - name: foreginKey + '?', - type: sourceModelPrimaryKeyType, - }; - return fkProperty; - } - - getHasMany(className, relationName) { - let relationProperty = { - decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], - name: relationName, - type: className + '[]', - }; - - return relationProperty; - } - - getHasOne(className, relationName) { - let relationProperty = { - decorators: [{name: 'hasOne', arguments: ['() => ' + className]}], - name: relationName, - type: className, - }; - return relationProperty; - } - - getBelongsTo(className, relationName, fktype) { - let relationProperty; - relationProperty = { - decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], - name: relationName, - type: fktype, - }; - return relationProperty; - } - - addRequiredImports(sourceFile, targetModel, relationType, targetClassName) { - let importsArray = this.getRequiredImports( - targetModel, - relationType, - targetClassName, - ); - while (importsArray.length > 0) { - let currentImport = importsArray.pop(); - this.addCurrentImport(sourceFile, currentImport); - } - } - - getRequiredImports(targetModel, relationType, targetClassName) { - let importsArray = [ - { - name: targetClassName, - module: './' + utils.kebabCase(targetModel) + '.model', - }, - { - name: relationType, - module: '@loopback/repository', - }, - ]; - - return importsArray; - } - - addCurrentImport(sourceFile, currentImport) { - if (!this.doesModuleExists(sourceFile, currentImport.module)) { - sourceFile.addImportDeclaration({ - moduleSpecifier: currentImport.module, - }); - } - if (!this.doesImportExistInModule(sourceFile, currentImport)) { - sourceFile - .getImportDeclarationOrThrow(currentImport.module) - .addNamedImport(currentImport.name); - } - } - - doesModuleExists(sourceFile, moduleName) { - return sourceFile.getImportDeclaration(moduleName); - } - - doesImportExistInModule(sourceFile, currentImport) { - let identicalImport; - let relevantImports = this.getNamedImportsFromModule( - sourceFile, - currentImport.module, - ); - if (relevantImports.length > 0) { - identicalImport = relevantImports[0] - .getNamedImports() - .filter(imp => imp.getName() == currentImport.name); - } - - return identicalImport && identicalImport.length > 0; - } - - getNamedImportsFromModule(sourceFile, moduleName) { - let allImports = sourceFile.getImportDeclarations(); - let relevantImports = allImports.filter( - imp => imp.getModuleSpecifierValue() == moduleName, - ); - return relevantImports; - } -}; diff --git a/packages/cli/generators/relation/relationBelongsTo.js b/packages/cli/generators/relation/relationBelongsTo.js index e2bf87e69851..19a934405c35 100644 --- a/packages/cli/generators/relation/relationBelongsTo.js +++ b/packages/cli/generators/relation/relationBelongsTo.js @@ -1,8 +1,10 @@ 'use strict'; +const ast = require('ts-simple-ast'); const path = require('path'); const RelationGenerator = require('./relation'); const utils = require('../../lib/utils'); +const relationUtils = require('./relationutils'); const CONTROLLER_TEMPLATE_PATH_BELONGS_TO = 'controller-relation-template-belongs-to.ts.ejs'; @@ -55,10 +57,68 @@ module.exports = class RelationBelongsTo extends RelationGenerator { } generateModels(options) { - throw new Error('Not implemented'); + let path = this.artifactInfo.modelDir; + let sourceModel = options.sourceModel; + let targetModel = options.destinationModel; + let relationType = options.relationType; + let relationName = options.relationName; + let fktype = options.sourceModelPrimaryKeyType; + + // add keyTo when needed in both hasMany and belongsTo relation + + let project = new ast.Project(); + + const sourceFile = relationUtils.addFileToProject( + project, + path, + utils.kebabCase(sourceModel), + ); + + const targetFile = relationUtils.addFileToProject( + project, + path, + utils.kebabCase(targetModel), + ); + + const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel); + + const targetClass = relationUtils.getClassObj(targetFile, targetModel); + + if (!relationUtils.isClassExist(sourceFile)) { + return; + } + if (!relationUtils.isClassExist(targetFile)) { + return; + } + + let modelProperty; + + + relationUtils.isRelationExist(sourceClass, relationName); + modelProperty = this.getBelongsTo( + targetModel, + relationName, + utils.toClassName(fktype), + ); + + + relationUtils.addPropertyToModel(sourceClass, modelProperty); + relationUtils.addRequiredImports(sourceFile, targetModel, relationType, targetModel); + sourceClass.formatText(); + sourceFile.save(); } generateRepositories(options) { throw new Error('Not implemented'); } + + getBelongsTo(className, relationName, fktype) { + let relationProperty; + relationProperty = { + decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], + name: relationName, + type: fktype, + }; + return relationProperty; + } }; diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index ed865543df0b..5e03e1e9b785 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -1,7 +1,9 @@ 'use strict'; +const ast = require('ts-simple-ast'); const path = require('path'); const RelationGenerator = require('./relation'); +const relationUtils = require('./relationutils'); const utils = require('../../lib/utils'); const CONTROLLER_TEMPLATE_PATH_HAS_MANY = @@ -50,10 +52,74 @@ module.exports = class RelationHasMany extends RelationGenerator { } generateModels(options) { - throw new Error('Not implemented'); + let path = this.artifactInfo.modelDir; + let sourceModel = options.sourceModel; + let targetModel = options.destinationModel; + let relationType = options.relationType; + let relationName = options.relationName; + let fktype = options.sourceModelPrimaryKeyType; + let foreignKey = options.defaultForeignKeyName; + let isForeignKeyExist = options.destinationModelForeignKeyExist; + + // add keyTo when needed in both hasMany and belongsTo relation + + let project = new ast.Project(); + + const sourceFile = relationUtils.addFileToProject( + project, + path, + utils.kebabCase(sourceModel), + ); + + const targetFile = relationUtils.addFileToProject( + project, + path, + utils.kebabCase(targetModel), + ); + + const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel); + + const targetClass = relationUtils.getClassObj(targetFile, targetModel); + + if (!relationUtils.isClassExist(sourceFile)) { + return; + } + if (!relationUtils.isClassExist(targetFile)) { + return; + } + + let modelProperty; + + relationUtils.isRelationExist(sourceClass, relationName); + if (isForeignKeyExist) { + relationUtils.vlidateType(targetClass, foreignKey, fktype); + } else { + modelProperty = relationUtils.addForeginKey(foreignKey, fktype); + relationUtils.addPropertyToModel(targetClass, modelProperty); + targetClass.formatText(); + targetFile.save(); + } + modelProperty = this.getHasMany(targetModel, relationName); + + + relationUtils.addPropertyToModel(sourceClass, modelProperty); + relationUtils.addRequiredImports(sourceFile, targetModel, relationType, targetModel); + sourceClass.formatText(); + sourceFile.save(); } generateRepositories(options) { throw new Error('Not implemented'); } + + + getHasMany(className, relationName) { + let relationProperty = { + decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], + name: relationName, + type: className + '[]', + }; + + return relationProperty; + } }; diff --git a/packages/cli/generators/relation/relationutils.js b/packages/cli/generators/relation/relationutils.js index dba9fa176dd5..f95da52527de 100644 --- a/packages/cli/generators/relation/relationutils.js +++ b/packages/cli/generators/relation/relationutils.js @@ -1,5 +1,152 @@ -exports.relationType = relationType = { +'use strict'; + +const utils = require('../../lib/utils'); + +exports.relationType = { belongsTo: 'belongsTo', hasMany: 'hasMany', hasOne: 'hasOne', }; + +exports.addFileToProject = function (project, path, modelName) { + const fileName = path + '/' + modelName + '.model.ts'; + return project.addExistingSourceFile(fileName); +}; + +exports.getClassObj = function (fileName, modelName) { + return fileName.getClassOrThrow(modelName); +}; + +exports.getClassesCount = function (fileName) { + return fileName.getClasses().length; +} + +exports.isClassExist = function (fileName) { + return this.getClassesCount(fileName) == 1; +}; + +exports.isPropertyExist = function (classObj, propertyName) { + return classObj.getProperties() + .map(x => x.getName()) + .includes(propertyName); +}; + +exports.isRelationExist = function (classObj, propertyName) { + if (this.isPropertyExist(classObj, propertyName)) { + console.log('property ' + propertyName + ' exsist in the model'); + throw new Error(' Property exsists'); + } + return; +} + +exports.vlidateType = function (classObj, foriegnKeyName, foriegnKeyType) { + if ( + utils.lowerCase( + classObj + .getProperty(foriegnKeyName) + .getType() + .getText(), + ) != foriegnKeyType + ) { + console.log(' foreignKey type has wrong Type '); + throw new Error('foreginKey Type Error'); + } + return; +}; + +exports.addForeginKey = function (foreginKey, sourceModelPrimaryKeyType) { + let fkProperty = { + decorators: [ + { + name: 'property', + arguments: ["{\n type : '" + sourceModelPrimaryKeyType + "',\n}"], + }, + ], + name: foreginKey + '?', + type: sourceModelPrimaryKeyType, + }; + return fkProperty; +} + +exports.addPropertyToModel = function (classOBj, modelProperty) { + classOBj.insertProperty(this.getPropertiesCount(classOBj), modelProperty); + classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); +} + +exports.getPropertiesCount = function (classObj) { + return classObj.getProperties().length; +} + +exports.getPropertyStartPos = function (classObj) { + return classObj + .getChildSyntaxList() + .getChildAtIndex(this.getPropertiesCount(classObj) - 1) + .getPos(); +}; + +exports.addRequiredImports = function (sourceFile, targetModel, relationType, targetClassName) { + let importsArray = this.getRequiredImports( + targetModel, + relationType, + targetClassName, + ); + while (importsArray.length > 0) { + let currentImport = importsArray.pop(); + this.addCurrentImport(sourceFile, currentImport); + } +}; + +exports.getRequiredImports = function (targetModel, relationType, targetClassName) { + let importsArray = [ + { + name: targetClassName, + module: './' + utils.kebabCase(targetModel) + '.model', + }, + { + name: relationType, + module: '@loopback/repository', + }, + ]; + + return importsArray; +}; + +exports.addCurrentImport = function (sourceFile, currentImport) { + if (!this.doesModuleExists(sourceFile, currentImport.module)) { + sourceFile.addImportDeclaration({ + moduleSpecifier: currentImport.module, + }); + } + if (!this.doesImportExistInModule(sourceFile, currentImport)) { + sourceFile + .getImportDeclarationOrThrow(currentImport.module) + .addNamedImport(currentImport.name); + } +} + +exports.doesModuleExists = function (sourceFile, moduleName) { + return sourceFile.getImportDeclaration(moduleName); +} + +exports.doesImportExistInModule = function (sourceFile, currentImport) { + let identicalImport; + let relevantImports = this.getNamedImportsFromModule( + sourceFile, + currentImport.module, + ); + if (relevantImports.length > 0) { + identicalImport = relevantImports[0] + .getNamedImports() + .filter(imp => imp.getName() == currentImport.name); + } + + return identicalImport && identicalImport.length > 0; +} + +exports.getNamedImportsFromModule = function (sourceFile, moduleName) { + let allImports = sourceFile.getImportDeclarations(); + let relevantImports = allImports.filter( + imp => imp.getModuleSpecifierValue() == moduleName, + ); + return relevantImports; +}; From 0ee01088c6702ada9159ce35fa647aa6e19f8715 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 12 Feb 2019 10:01:48 -0500 Subject: [PATCH 57/89] npm run lint:fix. --- packages/cli/generators/relation/index.js | 4 +- .../generators/relation/relationBelongsTo.js | 11 ++-- .../generators/relation/relationHasMany.js | 11 ++-- .../cli/generators/relation/relationutils.js | 62 +++++++++++-------- 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 5a5a37474694..49d6c3e59d76 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -248,8 +248,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } diff --git a/packages/cli/generators/relation/relationBelongsTo.js b/packages/cli/generators/relation/relationBelongsTo.js index 19a934405c35..f650a869d47b 100644 --- a/packages/cli/generators/relation/relationBelongsTo.js +++ b/packages/cli/generators/relation/relationBelongsTo.js @@ -93,7 +93,6 @@ module.exports = class RelationBelongsTo extends RelationGenerator { let modelProperty; - relationUtils.isRelationExist(sourceClass, relationName); modelProperty = this.getBelongsTo( targetModel, @@ -101,9 +100,13 @@ module.exports = class RelationBelongsTo extends RelationGenerator { utils.toClassName(fktype), ); - relationUtils.addPropertyToModel(sourceClass, modelProperty); - relationUtils.addRequiredImports(sourceFile, targetModel, relationType, targetModel); + relationUtils.addRequiredImports( + sourceFile, + targetModel, + relationType, + targetModel, + ); sourceClass.formatText(); sourceFile.save(); } @@ -115,7 +118,7 @@ module.exports = class RelationBelongsTo extends RelationGenerator { getBelongsTo(className, relationName, fktype) { let relationProperty; relationProperty = { - decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], + decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], name: relationName, type: fktype, }; diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index 5e03e1e9b785..fdf391bb2b74 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -101,9 +101,13 @@ module.exports = class RelationHasMany extends RelationGenerator { } modelProperty = this.getHasMany(targetModel, relationName); - relationUtils.addPropertyToModel(sourceClass, modelProperty); - relationUtils.addRequiredImports(sourceFile, targetModel, relationType, targetModel); + relationUtils.addRequiredImports( + sourceFile, + targetModel, + relationType, + targetModel, + ); sourceClass.formatText(); sourceFile.save(); } @@ -112,10 +116,9 @@ module.exports = class RelationHasMany extends RelationGenerator { throw new Error('Not implemented'); } - getHasMany(className, relationName) { let relationProperty = { - decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], + decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], name: relationName, type: className + '[]', }; diff --git a/packages/cli/generators/relation/relationutils.js b/packages/cli/generators/relation/relationutils.js index f95da52527de..aae9ffcb7d7b 100644 --- a/packages/cli/generators/relation/relationutils.js +++ b/packages/cli/generators/relation/relationutils.js @@ -8,38 +8,39 @@ exports.relationType = { hasOne: 'hasOne', }; -exports.addFileToProject = function (project, path, modelName) { +exports.addFileToProject = function(project, path, modelName) { const fileName = path + '/' + modelName + '.model.ts'; return project.addExistingSourceFile(fileName); }; -exports.getClassObj = function (fileName, modelName) { +exports.getClassObj = function(fileName, modelName) { return fileName.getClassOrThrow(modelName); }; -exports.getClassesCount = function (fileName) { +exports.getClassesCount = function(fileName) { return fileName.getClasses().length; -} +}; -exports.isClassExist = function (fileName) { +exports.isClassExist = function(fileName) { return this.getClassesCount(fileName) == 1; }; -exports.isPropertyExist = function (classObj, propertyName) { - return classObj.getProperties() +exports.isPropertyExist = function(classObj, propertyName) { + return classObj + .getProperties() .map(x => x.getName()) .includes(propertyName); }; -exports.isRelationExist = function (classObj, propertyName) { +exports.isRelationExist = function(classObj, propertyName) { if (this.isPropertyExist(classObj, propertyName)) { console.log('property ' + propertyName + ' exsist in the model'); throw new Error(' Property exsists'); } return; -} +}; -exports.vlidateType = function (classObj, foriegnKeyName, foriegnKeyType) { +exports.vlidateType = function(classObj, foriegnKeyName, foriegnKeyType) { if ( utils.lowerCase( classObj @@ -54,7 +55,7 @@ exports.vlidateType = function (classObj, foriegnKeyName, foriegnKeyType) { return; }; -exports.addForeginKey = function (foreginKey, sourceModelPrimaryKeyType) { +exports.addForeginKey = function(foreginKey, sourceModelPrimaryKeyType) { let fkProperty = { decorators: [ { @@ -66,25 +67,30 @@ exports.addForeginKey = function (foreginKey, sourceModelPrimaryKeyType) { type: sourceModelPrimaryKeyType, }; return fkProperty; -} +}; -exports.addPropertyToModel = function (classOBj, modelProperty) { +exports.addPropertyToModel = function(classOBj, modelProperty) { classOBj.insertProperty(this.getPropertiesCount(classOBj), modelProperty); classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); -} +}; -exports.getPropertiesCount = function (classObj) { +exports.getPropertiesCount = function(classObj) { return classObj.getProperties().length; -} +}; -exports.getPropertyStartPos = function (classObj) { +exports.getPropertyStartPos = function(classObj) { return classObj .getChildSyntaxList() .getChildAtIndex(this.getPropertiesCount(classObj) - 1) .getPos(); }; -exports.addRequiredImports = function (sourceFile, targetModel, relationType, targetClassName) { +exports.addRequiredImports = function( + sourceFile, + targetModel, + relationType, + targetClassName, +) { let importsArray = this.getRequiredImports( targetModel, relationType, @@ -96,7 +102,11 @@ exports.addRequiredImports = function (sourceFile, targetModel, relationType, ta } }; -exports.getRequiredImports = function (targetModel, relationType, targetClassName) { +exports.getRequiredImports = function( + targetModel, + relationType, + targetClassName, +) { let importsArray = [ { name: targetClassName, @@ -111,7 +121,7 @@ exports.getRequiredImports = function (targetModel, relationType, targetClassNam return importsArray; }; -exports.addCurrentImport = function (sourceFile, currentImport) { +exports.addCurrentImport = function(sourceFile, currentImport) { if (!this.doesModuleExists(sourceFile, currentImport.module)) { sourceFile.addImportDeclaration({ moduleSpecifier: currentImport.module, @@ -122,13 +132,13 @@ exports.addCurrentImport = function (sourceFile, currentImport) { .getImportDeclarationOrThrow(currentImport.module) .addNamedImport(currentImport.name); } -} +}; -exports.doesModuleExists = function (sourceFile, moduleName) { +exports.doesModuleExists = function(sourceFile, moduleName) { return sourceFile.getImportDeclaration(moduleName); -} +}; -exports.doesImportExistInModule = function (sourceFile, currentImport) { +exports.doesImportExistInModule = function(sourceFile, currentImport) { let identicalImport; let relevantImports = this.getNamedImportsFromModule( sourceFile, @@ -141,9 +151,9 @@ exports.doesImportExistInModule = function (sourceFile, currentImport) { } return identicalImport && identicalImport.length > 0; -} +}; -exports.getNamedImportsFromModule = function (sourceFile, moduleName) { +exports.getNamedImportsFromModule = function(sourceFile, moduleName) { let allImports = sourceFile.getImportDeclarations(); let relevantImports = allImports.filter( imp => imp.getModuleSpecifierValue() == moduleName, From 909ab66107a6ae1fa2ad8f5468acc75a415c3846 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 12 Feb 2019 10:24:18 -0500 Subject: [PATCH 58/89] Remvoed function isClassExist from relationutils. --- .../generators/relation/relationBelongsTo.js | 10 +----- .../generators/relation/relationHasMany.js | 10 +----- .../cli/generators/relation/relationutils.js | 36 +++++++++---------- 3 files changed, 18 insertions(+), 38 deletions(-) diff --git a/packages/cli/generators/relation/relationBelongsTo.js b/packages/cli/generators/relation/relationBelongsTo.js index f650a869d47b..10476298e389 100644 --- a/packages/cli/generators/relation/relationBelongsTo.js +++ b/packages/cli/generators/relation/relationBelongsTo.js @@ -81,16 +81,8 @@ module.exports = class RelationBelongsTo extends RelationGenerator { ); const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel); - const targetClass = relationUtils.getClassObj(targetFile, targetModel); - if (!relationUtils.isClassExist(sourceFile)) { - return; - } - if (!relationUtils.isClassExist(targetFile)) { - return; - } - let modelProperty; relationUtils.isRelationExist(sourceClass, relationName); @@ -118,7 +110,7 @@ module.exports = class RelationBelongsTo extends RelationGenerator { getBelongsTo(className, relationName, fktype) { let relationProperty; relationProperty = { - decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], + decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], name: relationName, type: fktype, }; diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index fdf391bb2b74..ce58a9915ad6 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -78,16 +78,8 @@ module.exports = class RelationHasMany extends RelationGenerator { ); const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel); - const targetClass = relationUtils.getClassObj(targetFile, targetModel); - if (!relationUtils.isClassExist(sourceFile)) { - return; - } - if (!relationUtils.isClassExist(targetFile)) { - return; - } - let modelProperty; relationUtils.isRelationExist(sourceClass, relationName); @@ -118,7 +110,7 @@ module.exports = class RelationHasMany extends RelationGenerator { getHasMany(className, relationName) { let relationProperty = { - decorators: [{name: 'hasMany', arguments: ['() => ' + className]}], + decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], name: relationName, type: className + '[]', }; diff --git a/packages/cli/generators/relation/relationutils.js b/packages/cli/generators/relation/relationutils.js index aae9ffcb7d7b..4284950ad96d 100644 --- a/packages/cli/generators/relation/relationutils.js +++ b/packages/cli/generators/relation/relationutils.js @@ -8,31 +8,27 @@ exports.relationType = { hasOne: 'hasOne', }; -exports.addFileToProject = function(project, path, modelName) { +exports.addFileToProject = function (project, path, modelName) { const fileName = path + '/' + modelName + '.model.ts'; return project.addExistingSourceFile(fileName); }; -exports.getClassObj = function(fileName, modelName) { +exports.getClassObj = function (fileName, modelName) { return fileName.getClassOrThrow(modelName); }; -exports.getClassesCount = function(fileName) { +exports.getClassesCount = function (fileName) { return fileName.getClasses().length; }; -exports.isClassExist = function(fileName) { - return this.getClassesCount(fileName) == 1; -}; - -exports.isPropertyExist = function(classObj, propertyName) { +exports.isPropertyExist = function (classObj, propertyName) { return classObj .getProperties() .map(x => x.getName()) .includes(propertyName); }; -exports.isRelationExist = function(classObj, propertyName) { +exports.isRelationExist = function (classObj, propertyName) { if (this.isPropertyExist(classObj, propertyName)) { console.log('property ' + propertyName + ' exsist in the model'); throw new Error(' Property exsists'); @@ -40,7 +36,7 @@ exports.isRelationExist = function(classObj, propertyName) { return; }; -exports.vlidateType = function(classObj, foriegnKeyName, foriegnKeyType) { +exports.vlidateType = function (classObj, foriegnKeyName, foriegnKeyType) { if ( utils.lowerCase( classObj @@ -55,7 +51,7 @@ exports.vlidateType = function(classObj, foriegnKeyName, foriegnKeyType) { return; }; -exports.addForeginKey = function(foreginKey, sourceModelPrimaryKeyType) { +exports.addForeginKey = function (foreginKey, sourceModelPrimaryKeyType) { let fkProperty = { decorators: [ { @@ -69,23 +65,23 @@ exports.addForeginKey = function(foreginKey, sourceModelPrimaryKeyType) { return fkProperty; }; -exports.addPropertyToModel = function(classOBj, modelProperty) { +exports.addPropertyToModel = function (classOBj, modelProperty) { classOBj.insertProperty(this.getPropertiesCount(classOBj), modelProperty); classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); }; -exports.getPropertiesCount = function(classObj) { +exports.getPropertiesCount = function (classObj) { return classObj.getProperties().length; }; -exports.getPropertyStartPos = function(classObj) { +exports.getPropertyStartPos = function (classObj) { return classObj .getChildSyntaxList() .getChildAtIndex(this.getPropertiesCount(classObj) - 1) .getPos(); }; -exports.addRequiredImports = function( +exports.addRequiredImports = function ( sourceFile, targetModel, relationType, @@ -102,7 +98,7 @@ exports.addRequiredImports = function( } }; -exports.getRequiredImports = function( +exports.getRequiredImports = function ( targetModel, relationType, targetClassName, @@ -121,7 +117,7 @@ exports.getRequiredImports = function( return importsArray; }; -exports.addCurrentImport = function(sourceFile, currentImport) { +exports.addCurrentImport = function (sourceFile, currentImport) { if (!this.doesModuleExists(sourceFile, currentImport.module)) { sourceFile.addImportDeclaration({ moduleSpecifier: currentImport.module, @@ -134,11 +130,11 @@ exports.addCurrentImport = function(sourceFile, currentImport) { } }; -exports.doesModuleExists = function(sourceFile, moduleName) { +exports.doesModuleExists = function (sourceFile, moduleName) { return sourceFile.getImportDeclaration(moduleName); }; -exports.doesImportExistInModule = function(sourceFile, currentImport) { +exports.doesImportExistInModule = function (sourceFile, currentImport) { let identicalImport; let relevantImports = this.getNamedImportsFromModule( sourceFile, @@ -153,7 +149,7 @@ exports.doesImportExistInModule = function(sourceFile, currentImport) { return identicalImport && identicalImport.length > 0; }; -exports.getNamedImportsFromModule = function(sourceFile, moduleName) { +exports.getNamedImportsFromModule = function (sourceFile, moduleName) { let allImports = sourceFile.getImportDeclarations(); let relevantImports = allImports.filter( imp => imp.getModuleSpecifierValue() == moduleName, From 26a7f2b551a4e60fd495976febfc82f2415aa249 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Wed, 13 Feb 2019 16:46:50 +0200 Subject: [PATCH 59/89] added keyTo to hasMany when using custom foreignkey --- packages/cli/generators/relation/index.js | 6 +++--- .../cli/generators/relation/relationHasMany.js | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 49d6c3e59d76..078d0448c1bc 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -248,8 +248,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } @@ -354,7 +354,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { when: !this.artifactInfo.destinationModelForeignKeyName, }, ]); - this.options.destinationModelForeignKeyName = this.artifactInfo.destinationModelForeignKeyName; + this.options.destinationModelForeignKeyName = this.artifactInfo.destinationModelForeignKeyName.value; } else { this.options.destinationModelForeignKeyName = this.options.defaultForeignKeyName; } diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index ce58a9915ad6..21ab81269915 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -63,8 +63,14 @@ module.exports = class RelationHasMany extends RelationGenerator { // add keyTo when needed in both hasMany and belongsTo relation + if (!isForeignKeyExist) { + foreignKey = options.destinationModelForeignKeyName + } + let isDefaultForeignKey = (options.defaultForeignKeyName == options.destinationModelForeignKeyName) + let project = new ast.Project(); + const sourceFile = relationUtils.addFileToProject( project, path, @@ -91,7 +97,7 @@ module.exports = class RelationHasMany extends RelationGenerator { targetClass.formatText(); targetFile.save(); } - modelProperty = this.getHasMany(targetModel, relationName); + modelProperty = this.getHasMany(targetModel, relationName, isDefaultForeignKey, foreignKey); relationUtils.addPropertyToModel(sourceClass, modelProperty); relationUtils.addRequiredImports( @@ -108,13 +114,17 @@ module.exports = class RelationHasMany extends RelationGenerator { throw new Error('Not implemented'); } - getHasMany(className, relationName) { + getHasMany(className, relationName, isDefaultForeignKey, foreignKey) { + let relationDecoretor = [{ name: 'hasMany', arguments: ['() => ' + className + " ,{keyTo: '" + foreignKey + "'}"] }] + if (isDefaultForeignKey) { + relationDecoretor = [{ name: 'hasMany', arguments: ['() => ' + className] }] + } let relationProperty = { - decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], + // decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], + decorators: relationDecoretor, name: relationName, type: className + '[]', }; - return relationProperty; } }; From 5fa611888cc2c3657a4ec51807c18f85794ac7c6 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Sun, 17 Feb 2019 11:21:12 +0200 Subject: [PATCH 60/89] removed comments for adding keyTo --- packages/cli/generators/relation/relationHasMany.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index 21ab81269915..04eb34d23993 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -61,7 +61,6 @@ module.exports = class RelationHasMany extends RelationGenerator { let foreignKey = options.defaultForeignKeyName; let isForeignKeyExist = options.destinationModelForeignKeyExist; - // add keyTo when needed in both hasMany and belongsTo relation if (!isForeignKeyExist) { foreignKey = options.destinationModelForeignKeyName @@ -120,7 +119,6 @@ module.exports = class RelationHasMany extends RelationGenerator { relationDecoretor = [{ name: 'hasMany', arguments: ['() => ' + className] }] } let relationProperty = { - // decorators: [{ name: 'hasMany', arguments: ['() => ' + className] }], decorators: relationDecoretor, name: relationName, type: className + '[]', From bd71349bd018e6d61b2726adbe9fdbe3d807b568 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 17 Feb 2019 11:03:01 +0000 Subject: [PATCH 61/89] cli relations: replaced param name: relationBaseClass => relationType. --- packages/cli/generators/relation/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 078d0448c1bc..718e1cf358eb 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -273,14 +273,14 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.artifactInfo.relationType = await this.prompt([ { type: 'list', - name: 'relationBaseClass', + name: 'relationType', message: PROMPT_BASE_RELATION_CLASS, choices: Object.keys(relationUtils.relationType), when: !this.artifactInfo.relationType, validate: utils.validateClassName, }, ]); - this.options.relationType = this.artifactInfo.relationType.relationBaseClass; + this.options.relationType = this.artifactInfo.relationType.relationType; return this.artifactInfo.relationType; } From 2795fa66d59040fdfe44148e93b170565d9c6ff2 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 17 Feb 2019 11:05:44 +0000 Subject: [PATCH 62/89] cli relations: replaced param name: modelNameList => parameter. --- packages/cli/generators/relation/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 718e1cf358eb..da6485cf99cf 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -258,13 +258,15 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.artifactInfo[parameter] = await this.prompt([ { type: 'list', - name: 'modelNameList', + name: parameter, message: message, choices: modelList, when: this.artifactInfo.modelNameList === undefined, }, ]); - this.options[parameter] = this.artifactInfo[parameter].modelNameList; + + this.options[parameter] = this.artifactInfo[parameter][parameter]; + return this.artifactInfo[parameter]; } From 848f774382ba999a5d2d73d0fecc625a0708b4f0 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Sun, 17 Feb 2019 11:13:41 +0000 Subject: [PATCH 63/89] cli relations: updated _calcSourceModelPrimaryKeyType. --- packages/cli/generators/relation/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index da6485cf99cf..98f66b6939b7 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -106,7 +106,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { const sourceFile = path.join( this.artifactInfo.modelDir, - utils.getModelFileName(this.artifactInfo.sourceModel.modelNameList), + utils.getModelFileName(this.options.sourceModel), ); const sf = project.addExistingSourceFile(sourceFile); this.options.sourceModelPrimaryKeyType = this._getKeyType( From 597668759f4f5e0e4d638973e8677328708c9aab Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 00:24:33 -0800 Subject: [PATCH 64/89] Added to generator relation setOptions. --- packages/cli/generators/relation/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 98f66b6939b7..663a761318a8 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -176,6 +176,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { */ return; } + + setOptions() { + return super.setOptions(); + } _setupGenerator() { this.artifactInfo = { From c32e0c36a1faabef4396673c001619c80c0d18be Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 01:26:16 -0800 Subject: [PATCH 65/89] Added src ERROR_NO_MODELS_FOUND to relation/index.js. --- packages/cli/generators/relation/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 663a761318a8..9dd1052b4be2 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -23,8 +23,9 @@ const RelationHasOne = require('./relationHasOne'); const RepositoryRelation = require('./repositoryRelation'); const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; -const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; +const ERROR_NO_MODELS_FOUND = 'Model was not found in'; +const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE_SOURCE_MODEL = 'Please select source model'; From d1f549eba49df405fa27216774b21deb4d279201 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 01:30:59 -0800 Subject: [PATCH 66/89] Removed redundant option from class RelationGenerator. --- packages/cli/generators/relation/relation.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/cli/generators/relation/relation.js b/packages/cli/generators/relation/relation.js index 6464c4f527e9..b9c0d3dea65e 100644 --- a/packages/cli/generators/relation/relation.js +++ b/packages/cli/generators/relation/relation.js @@ -28,12 +28,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.artifactInfo.rootDir, 'repositories', ); - - this.option('controllerType', { - type: String, - required: false, - description: 'Type for the ' + this.artifactInfo.type, - }); } scaffold() { From 05cbb28a55267d6c2460fd66f537f719fd08099d Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 01:35:06 -0800 Subject: [PATCH 67/89] Removed unused variabled. --- packages/cli/generators/relation/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 9dd1052b4be2..f18e8d9042bd 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -37,9 +37,6 @@ const relPathControllersFolder = '/controllers'; const relPathModelsFolder = '/models'; const relPathRepositoriesFolder = '/repositories'; -let args; -let opts; - module.exports = class RelationGenerator extends ArtifactGenerator { constructor(args, opts) { super(args, opts); From f088782a5b013efb7cf5d2cd72985162d875e90b Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 02:51:49 -0800 Subject: [PATCH 68/89] Added options to relation generator. --- packages/cli/generators/relation/index.js | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index f18e8d9042bd..80fd885a5240 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -42,6 +42,36 @@ module.exports = class RelationGenerator extends ArtifactGenerator { super(args, opts); this.args = args; this.opts = opts; + + this.option('relationType', { + type: String, + required: false, + description: 'Relation type', + }); + + this.option('sourceModel', { + type: String, + required: false, + description: 'Source model', + }); + + this.option('destinationModel', { + type: String, + required: false, + description: 'Destination model', + }); + + this.option('foreignKeyName', { + type: String, + required: false, + description: 'Destination model foreign key name', + }); + + this.option('relationName', { + type: String, + required: false, + description: 'Relation name', + }); } /** From 1d7e8b0afbfdd9523c323145fb3caa64743c6e27 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 03:32:43 -0800 Subject: [PATCH 69/89] Fixed prompt relation type. --- packages/cli/generators/relation/index.js | 32 ++++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 80fd885a5240..d4312b8024ed 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -303,19 +303,37 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } // Prompt a user for Relation type - async promptRelationBaseClassName() { - this.artifactInfo.relationType = await this.prompt([ + async promptRelationType() { + if (this.options.relationType) { + debug( + `Relation type received from command line: ${ + this.options.relationType + }`, + ); + this.artifactInfo.relationType = this.options.relationType; + } + + const relationTypeChoices = Object.keys(relationUtils.relationType); + return this.prompt([ { type: 'list', name: 'relationType', message: PROMPT_BASE_RELATION_CLASS, - choices: Object.keys(relationUtils.relationType), - when: !this.artifactInfo.relationType, + choices: relationTypeChoices, + when: this.artifactInfo.relationType === undefined, validate: utils.validateClassName, + default: relationTypeChoices[0], }, - ]); - this.options.relationType = this.artifactInfo.relationType.relationType; - return this.artifactInfo.relationType; + ]) + .then(props => { + debug(`props after relation type prompt: ${inspect(props)}`); + Object.assign(this.artifactInfo, props); + return props; + }) + .catch(err => { + debug(`Error during relation type prompt: ${err.stack}`); + return this.exit(err); + }); } // Get model list for source model. From 7452ee528e0d2f99ccaf3492b2dc1ecd8daf9224 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 03:52:34 -0800 Subject: [PATCH 70/89] Fixed sourceModel and destinationModel prompts. --- packages/cli/generators/relation/index.js | 29 +++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index d4312b8024ed..33bcd5667fd5 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -286,20 +286,35 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); } + if (this.options[parameter]) { + debug( + `${parameter} received from command line: ${ + this.options[parameter] + }`, + ); + this.artifactInfo[parameter] = this.options[parameter]; + } + // Prompt a user for model. - this.artifactInfo[parameter] = await this.prompt([ + return this.prompt([ { type: 'list', name: parameter, message: message, choices: modelList, - when: this.artifactInfo.modelNameList === undefined, + when: this.artifactInfo[parameter] === undefined, + default: modelList[0] }, - ]); - - this.options[parameter] = this.artifactInfo[parameter][parameter]; - - return this.artifactInfo[parameter]; + ]) + .then(props => { + debug(`props after ${parameter} prompt: ${inspect(props)}`); + Object.assign(this.artifactInfo, props); + return props; + }) + .catch(err => { + debug(`Error during ${parameter} prompt: ${err.stack}`); + return this.exit(err); + }); } // Prompt a user for Relation type From 4cb18a16851bf9b49f04a1fc4875723841756ac2 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 05:03:03 -0800 Subject: [PATCH 71/89] Fixed relation name prompt. --- packages/cli/generators/relation/index.js | 35 +++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 33bcd5667fd5..ab4cb3142793 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -152,7 +152,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { utils.toClassName(this.options.sourceModelPrimaryKey); } - async _scaffold() { + async scaffold() { let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; @@ -319,6 +319,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { // Prompt a user for Relation type async promptRelationType() { + if (this.shouldExit()) return false; + if (this.options.relationType) { debug( `Relation type received from command line: ${ @@ -432,18 +434,33 @@ module.exports = class RelationGenerator extends ArtifactGenerator { const defaultRelationName = this._getDefaultRelationName(); - this.artifactInfo.relationName = await this.prompt([ + if (this.options.relationName) { + debug( + `Relation name received from command line: ${ + this.options.relationName + }`, + ); + this.artifactInfo.relationName = this.options.relationName; + } + + + return this.prompt([ { type: 'string', - name: 'value', + name: 'relationName', message: PROMPT_MESSAGE_PROPERTY_NAME, + when: this.artifactInfo.relationName === undefined, default: defaultRelationName, - when: !this.artifactInfo.relationName, }, - ]); - this.options.relationName = this.artifactInfo.relationName.value; - - //Generate this repository - await this._scaffold(); + ]) + .then(props => { + debug(`props after relation name prompt: ${inspect(props)}`); + Object.assign(this.artifactInfo, props); + return props; + }) + .catch(err => { + debug(`Error during relation name prompt: ${err.stack}`); + return this.exit(err); + }); } }; From 6372a3ab38f64d040b89e6c62d3d962918662e36 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 05:06:35 -0800 Subject: [PATCH 72/89] Code refactoring. --- packages/cli/generators/relation/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index ab4cb3142793..67e81c8832b2 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -432,8 +432,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { async promptRelationName() { if (this.shouldExit()) return false; - const defaultRelationName = this._getDefaultRelationName(); - if (this.options.relationName) { debug( `Relation name received from command line: ${ @@ -443,14 +441,13 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this.artifactInfo.relationName = this.options.relationName; } - return this.prompt([ { type: 'string', name: 'relationName', message: PROMPT_MESSAGE_PROPERTY_NAME, when: this.artifactInfo.relationName === undefined, - default: defaultRelationName, + default: this._getDefaultRelationName(), }, ]) .then(props => { From 5b6408b60d6c4408f52009e949ffcea18487af0b Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 06:04:40 -0800 Subject: [PATCH 73/89] Moved scaffold function to end of file. --- packages/cli/generators/relation/index.js | 106 +++++++++++----------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 67e81c8832b2..a3ad9439d375 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -151,59 +151,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { utils.camelCase(this.options.sourceModel) + utils.toClassName(this.options.sourceModelPrimaryKey); } - - async scaffold() { - let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; - let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; - let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; - - if (!this.options.relationType) { - throw new Error("'relationType' parameters should be specified."); - } - if (this.options.sourceModel === this.options.destinationModel) { - throw new Error( - "'sourceModel' and 'destinationModel' parameter values should be different.", - ); - } - debug('Invoke Controller generator...'); - - var relation; - - this.artifactInfo.name = this.options.relationType; - this.artifactInfo.relPath = relPathCtrl; - - switch (this.options.relationType) { - case relationUtils.relationType.belongsTo: - relation = new RelationBelongsTo(this.args, this.opts); - break; - case relationUtils.relationType.hasMany: - relation = new RelationHasMany(this.args, this.opts); - break; - case relationUtils.relationType.hasOne: - relation = new RelationHasOne(this.args, this.opts); - break; - default: - throw new Error(ERROR_INCORRECT_RELATION_TYPE); - } - - relation.generateControllers(this.options); - - debug('Invoke Model generator...'); - this.artifactInfo.relPath = relPathModel; - relation.generateModels(this.options); - /* - debug('Invoke Repository generator...'); - let repo = new RepositoryRelation(this.args, this.opts); - this.artifactInfo.relPath = relPathRepo; - repo.generateRelationRepository( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); - */ - return; - } setOptions() { return super.setOptions(); @@ -460,4 +407,57 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return this.exit(err); }); } + + async scaffold() { + let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; + let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; + let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; + + if (!this.options.relationType) { + throw new Error("'relationType' parameters should be specified."); + } + if (this.options.sourceModel === this.options.destinationModel) { + throw new Error( + "'sourceModel' and 'destinationModel' parameter values should be different.", + ); + } + debug('Invoke Controller generator...'); + + var relation; + + this.artifactInfo.name = this.options.relationType; + this.artifactInfo.relPath = relPathCtrl; + + switch (this.options.relationType) { + case relationUtils.relationType.belongsTo: + relation = new RelationBelongsTo(this.args, this.opts); + break; + case relationUtils.relationType.hasMany: + relation = new RelationHasMany(this.args, this.opts); + break; + case relationUtils.relationType.hasOne: + relation = new RelationHasOne(this.args, this.opts); + break; + default: + throw new Error(ERROR_INCORRECT_RELATION_TYPE); + } + + relation.generateControllers(this.options); + + debug('Invoke Model generator...'); + this.artifactInfo.relPath = relPathModel; + relation.generateModels(this.options); + /* + debug('Invoke Repository generator...'); + let repo = new RepositoryRelation(this.args, this.opts); + this.artifactInfo.relPath = relPathRepo; + repo.generateRelationRepository( + this.options.sourceModel, + this.options.destinationModel, + this.options.foreignKey, + this.options.relationType, + ); + */ + return; + } }; From ef90b04e33cdbb8898ea88b02e857f358b25ed91 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 07:12:28 -0800 Subject: [PATCH 74/89] Code refactoring. --- packages/cli/generators/relation/index.js | 89 ++++++++++++++--------- 1 file changed, 56 insertions(+), 33 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index a3ad9439d375..32150bd26ad6 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -26,6 +26,7 @@ const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; const ERROR_NO_MODELS_FOUND = 'Model was not found in'; const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; +const ERROR_SOURCE_MODEL_PRIMARY_KEY_DOES_NOT_EXIST = 'Source model primary key does not exist.'; const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE_SOURCE_MODEL = 'Please select source model'; @@ -115,12 +116,12 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } async _calcSourceModelPrimaryKey() { - this.options.sourceModelPrimaryKey = await this._getModelPrimaryKeyProperty( - this.options.sourceModel, + this.artifactInfo.sourceModelPrimaryKey = await this._getModelPrimaryKeyProperty( + this.artifactInfo.sourceModel, ); - if (this.options.sourceModelPrimaryKey === null) { - throw new Error('Source model primary key does not exist.'); + if (this.artifactInfo.sourceModelPrimaryKey === null) { + throw new Error(ERROR_SOURCE_MODEL_PRIMARY_KEY_DOES_NOT_EXIST); } } @@ -134,12 +135,12 @@ module.exports = class RelationGenerator extends ArtifactGenerator { const sourceFile = path.join( this.artifactInfo.modelDir, - utils.getModelFileName(this.options.sourceModel), + utils.getModelFileName(this.artifactInfo.sourceModel), ); const sf = project.addExistingSourceFile(sourceFile); - this.options.sourceModelPrimaryKeyType = this._getKeyType( + this.artifactInfo.sourceModelPrimaryKeyType = this._getKeyType( sf, - this.options.sourceModelPrimaryKey, + this.artifactInfo.sourceModelPrimaryKey, ); } @@ -147,9 +148,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { * Generate default foreign key name. Foreign key name use in target model. */ _calcDefaultForeignKey() { - this.options.defaultForeignKeyName = - utils.camelCase(this.options.sourceModel) + - utils.toClassName(this.options.sourceModelPrimaryKey); + this.artifactInfo.defaultForeignKeyName = + utils.camelCase(this.artifactInfo.sourceModel) + + utils.toClassName(this.artifactInfo.sourceModelPrimaryKey); } setOptions() { @@ -331,12 +332,12 @@ module.exports = class RelationGenerator extends ArtifactGenerator { */ async promptForeignKey() { if (this.shouldExit()) return false; - - if (_.isEmpty(this.options.sourceModel)) { + + if (_.isEmpty(this.artifactInfo.sourceModel)) { return this.exit(new Error(`${ERROR_NO_SOURCE_MODEL_SELECTED}`)); } - if (_.isEmpty(this.options.destinationModel)) { + if (_.isEmpty(this.artifactInfo.destinationModel)) { return this.exit(new Error(`${ERROR_NO_DESTINATION_MODEL_SELECTED}`)); } @@ -344,35 +345,52 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this._calcSourceModelPrimaryKeyType(); this._calcDefaultForeignKey(); - if (this.options.relationType === relationUtils.relationType.belongsTo) { + if (this.artifactInfo.relationType === relationUtils.relationType.belongsTo) { return; } let project = new ast.Project(); const destinationFile = path.join( this.artifactInfo.modelDir, - utils.getModelFileName(this.options.destinationModel), + utils.getModelFileName(this.artifactInfo.destinationModel), ); const df = project.addExistingSourceFile(destinationFile); const cl = this._getClassObj(df); - this.options.destinationModelForeignKeyExist = cl + this.artifactInfo.destinationModelForeignKeyExist = cl .getProperties() .map(x => x.getName()) - .includes(this.options.defaultForeignKeyName); - - if (!this.options.destinationModelForeignKeyExist) { - this.artifactInfo.destinationModelForeignKeyName = await this.prompt([ + .includes(this.artifactInfo.defaultForeignKeyName); + + if (!this.artifactInfo.destinationModelForeignKeyExist) { + if (this.options.foreignKeyName) { + debug( + `Foreign key name received from command line: ${ + this.options.foreignKeyName + }`, + ); + this.artifactInfo.foreignKeyName = this.options.foreignKeyName; + } + + return this.prompt([ { type: 'string', - name: 'value', + name: 'foreignKeyName', message: PROMPT_MESSAGE_FOREIGN_KEY_NAME, - default: this.options.defaultForeignKeyName, - when: !this.artifactInfo.destinationModelForeignKeyName, + default: this.artifactInfo.defaultForeignKeyName, + when: this.artifactInfo.foreignKeyName === undefined, }, - ]); - this.options.destinationModelForeignKeyName = this.artifactInfo.destinationModelForeignKeyName.value; + ]) + .then(props => { + debug(`props after foreign key name prompt: ${inspect(props)}`); + Object.assign(this.artifactInfo, props); + return props; + }) + .catch(err => { + debug(`Error during foreign key name prompt: ${err.stack}`); + return this.exit(err); + }); } else { - this.options.destinationModelForeignKeyName = this.options.defaultForeignKeyName; + this.artifactInfo.foreignKeyName = this.artifactInfo.defaultForeignKeyName; } } @@ -408,15 +426,15 @@ module.exports = class RelationGenerator extends ArtifactGenerator { }); } - async scaffold() { + scaffold() { let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; - if (!this.options.relationType) { + if (!this.artifactInfo.relationType) { throw new Error("'relationType' parameters should be specified."); } - if (this.options.sourceModel === this.options.destinationModel) { + if (this.artifactInfo.sourceModel === this.artifactInfo.destinationModel) { throw new Error( "'sourceModel' and 'destinationModel' parameter values should be different.", ); @@ -425,10 +443,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { var relation; - this.artifactInfo.name = this.options.relationType; + this.artifactInfo.name = this.artifactInfo.relationType; this.artifactInfo.relPath = relPathCtrl; - switch (this.options.relationType) { + switch (this.artifactInfo.relationType) { case relationUtils.relationType.belongsTo: relation = new RelationBelongsTo(this.args, this.opts); break; @@ -442,11 +460,11 @@ module.exports = class RelationGenerator extends ArtifactGenerator { throw new Error(ERROR_INCORRECT_RELATION_TYPE); } - relation.generateControllers(this.options); + relation.generateControllers(this.artifactInfo); debug('Invoke Model generator...'); this.artifactInfo.relPath = relPathModel; - relation.generateModels(this.options); + relation.generateModels(this.artifactInfo); /* debug('Invoke Repository generator...'); let repo = new RepositoryRelation(this.args, this.opts); @@ -460,4 +478,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { */ return; } + + async end() { + await super.end(); + } + }; From 4c6102d69c4e9913a4f05f99ec1be17c7866b084 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 07:28:49 -0800 Subject: [PATCH 75/89] Code cleanup. --- packages/cli/generators/relation/index.js | 23 +------------------ packages/cli/generators/relation/relation.js | 7 ++++++ .../generators/relation/relationHasMany.js | 2 +- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 32150bd26ad6..420ff81ce841 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -427,10 +427,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } scaffold() { - let relPathCtrl = this.artifactInfo.relPath + relPathControllersFolder; - let relPathModel = this.artifactInfo.relPath + relPathModelsFolder; - let relPathRepo = this.artifactInfo.relPath + relPathRepositoriesFolder; - if (!this.artifactInfo.relationType) { throw new Error("'relationType' parameters should be specified."); } @@ -444,7 +440,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { var relation; this.artifactInfo.name = this.artifactInfo.relationType; - this.artifactInfo.relPath = relPathCtrl; switch (this.artifactInfo.relationType) { case relationUtils.relationType.belongsTo: @@ -460,23 +455,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { throw new Error(ERROR_INCORRECT_RELATION_TYPE); } - relation.generateControllers(this.artifactInfo); - - debug('Invoke Model generator...'); - this.artifactInfo.relPath = relPathModel; - relation.generateModels(this.artifactInfo); - /* - debug('Invoke Repository generator...'); - let repo = new RepositoryRelation(this.args, this.opts); - this.artifactInfo.relPath = relPathRepo; - repo.generateRelationRepository( - this.options.sourceModel, - this.options.destinationModel, - this.options.foreignKey, - this.options.relationType, - ); - */ - return; + relation.generateAll(this.artifactInfo); } async end() { diff --git a/packages/cli/generators/relation/relation.js b/packages/cli/generators/relation/relation.js index b9c0d3dea65e..1be4ab89c136 100644 --- a/packages/cli/generators/relation/relation.js +++ b/packages/cli/generators/relation/relation.js @@ -37,6 +37,13 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return; } + + generateAll(options) { + this.generateControllers(options); + this.generateModels(options); + + } + generateControllers(options) { throw new Error('Not implemented'); } diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index 04eb34d23993..2022bf26e93d 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -63,7 +63,7 @@ module.exports = class RelationHasMany extends RelationGenerator { if (!isForeignKeyExist) { - foreignKey = options.destinationModelForeignKeyName + foreignKey = options.foreignKeyName } let isDefaultForeignKey = (options.defaultForeignKeyName == options.destinationModelForeignKeyName) From d1d76bb56312f64657dc656efac7d45f4422d6d3 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Mon, 18 Feb 2019 07:33:23 -0800 Subject: [PATCH 76/89] tslint:fix. --- packages/cli/generators/relation/index.js | 68 +++++++++---------- packages/cli/generators/relation/relation.js | 9 ++- .../generators/relation/relationBelongsTo.js | 2 +- .../generators/relation/relationHasMany.js | 25 +++++-- .../cli/generators/relation/relationutils.js | 32 ++++----- 5 files changed, 73 insertions(+), 63 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 420ff81ce841..a09f38eed778 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -26,7 +26,8 @@ const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; const ERROR_NO_MODELS_FOUND = 'Model was not found in'; const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; -const ERROR_SOURCE_MODEL_PRIMARY_KEY_DOES_NOT_EXIST = 'Source model primary key does not exist.'; +const ERROR_SOURCE_MODEL_PRIMARY_KEY_DOES_NOT_EXIST = + 'Source model primary key does not exist.'; const PROMPT_BASE_RELATION_CLASS = 'Please select the relation type'; const PROMPT_MESSAGE_SOURCE_MODEL = 'Please select source model'; @@ -152,10 +153,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { utils.camelCase(this.artifactInfo.sourceModel) + utils.toClassName(this.artifactInfo.sourceModelPrimaryKey); } - - setOptions() { - return super.setOptions(); - } + + setOptions() { + return super.setOptions(); + } _setupGenerator() { this.artifactInfo = { @@ -228,17 +229,15 @@ module.exports = class RelationGenerator extends ArtifactGenerator { new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( - 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', - )}`, + 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', + )}`, ), ); } if (this.options[parameter]) { debug( - `${parameter} received from command line: ${ - this.options[parameter] - }`, + `${parameter} received from command line: ${this.options[parameter]}`, ); this.artifactInfo[parameter] = this.options[parameter]; } @@ -251,10 +250,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { message: message, choices: modelList, when: this.artifactInfo[parameter] === undefined, - default: modelList[0] + default: modelList[0], }, - ]) - .then(props => { + ]) + .then(props => { debug(`props after ${parameter} prompt: ${inspect(props)}`); Object.assign(this.artifactInfo, props); return props; @@ -289,7 +288,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { validate: utils.validateClassName, default: relationTypeChoices[0], }, - ]) + ]) .then(props => { debug(`props after relation type prompt: ${inspect(props)}`); Object.assign(this.artifactInfo, props); @@ -332,7 +331,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { */ async promptForeignKey() { if (this.shouldExit()) return false; - + if (_.isEmpty(this.artifactInfo.sourceModel)) { return this.exit(new Error(`${ERROR_NO_SOURCE_MODEL_SELECTED}`)); } @@ -345,7 +344,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { this._calcSourceModelPrimaryKeyType(); this._calcDefaultForeignKey(); - if (this.artifactInfo.relationType === relationUtils.relationType.belongsTo) { + if ( + this.artifactInfo.relationType === relationUtils.relationType.belongsTo + ) { return; } let project = new ast.Project(); @@ -362,14 +363,14 @@ module.exports = class RelationGenerator extends ArtifactGenerator { .includes(this.artifactInfo.defaultForeignKeyName); if (!this.artifactInfo.destinationModelForeignKeyExist) { - if (this.options.foreignKeyName) { - debug( - `Foreign key name received from command line: ${ - this.options.foreignKeyName - }`, - ); - this.artifactInfo.foreignKeyName = this.options.foreignKeyName; - } + if (this.options.foreignKeyName) { + debug( + `Foreign key name received from command line: ${ + this.options.foreignKeyName + }`, + ); + this.artifactInfo.foreignKeyName = this.options.foreignKeyName; + } return this.prompt([ { @@ -380,15 +381,15 @@ module.exports = class RelationGenerator extends ArtifactGenerator { when: this.artifactInfo.foreignKeyName === undefined, }, ]) - .then(props => { - debug(`props after foreign key name prompt: ${inspect(props)}`); - Object.assign(this.artifactInfo, props); - return props; - }) - .catch(err => { - debug(`Error during foreign key name prompt: ${err.stack}`); - return this.exit(err); - }); + .then(props => { + debug(`props after foreign key name prompt: ${inspect(props)}`); + Object.assign(this.artifactInfo, props); + return props; + }) + .catch(err => { + debug(`Error during foreign key name prompt: ${err.stack}`); + return this.exit(err); + }); } else { this.artifactInfo.foreignKeyName = this.artifactInfo.defaultForeignKeyName; } @@ -461,5 +462,4 @@ module.exports = class RelationGenerator extends ArtifactGenerator { async end() { await super.end(); } - }; diff --git a/packages/cli/generators/relation/relation.js b/packages/cli/generators/relation/relation.js index 1be4ab89c136..61d186e26e29 100644 --- a/packages/cli/generators/relation/relation.js +++ b/packages/cli/generators/relation/relation.js @@ -38,11 +38,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return; } - generateAll(options) { - this.generateControllers(options); - this.generateModels(options); - - } + generateAll(options) { + this.generateControllers(options); + this.generateModels(options); + } generateControllers(options) { throw new Error('Not implemented'); diff --git a/packages/cli/generators/relation/relationBelongsTo.js b/packages/cli/generators/relation/relationBelongsTo.js index 10476298e389..c8e9a8e7fdff 100644 --- a/packages/cli/generators/relation/relationBelongsTo.js +++ b/packages/cli/generators/relation/relationBelongsTo.js @@ -110,7 +110,7 @@ module.exports = class RelationBelongsTo extends RelationGenerator { getBelongsTo(className, relationName, fktype) { let relationProperty; relationProperty = { - decorators: [{ name: 'belongsTo', arguments: ['() => ' + className] }], + decorators: [{name: 'belongsTo', arguments: ['() => ' + className]}], name: relationName, type: fktype, }; diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index 2022bf26e93d..e235e7173e19 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -61,15 +61,14 @@ module.exports = class RelationHasMany extends RelationGenerator { let foreignKey = options.defaultForeignKeyName; let isForeignKeyExist = options.destinationModelForeignKeyExist; - if (!isForeignKeyExist) { - foreignKey = options.foreignKeyName + foreignKey = options.foreignKeyName; } - let isDefaultForeignKey = (options.defaultForeignKeyName == options.destinationModelForeignKeyName) + let isDefaultForeignKey = + options.defaultForeignKeyName == options.destinationModelForeignKeyName; let project = new ast.Project(); - const sourceFile = relationUtils.addFileToProject( project, path, @@ -96,7 +95,12 @@ module.exports = class RelationHasMany extends RelationGenerator { targetClass.formatText(); targetFile.save(); } - modelProperty = this.getHasMany(targetModel, relationName, isDefaultForeignKey, foreignKey); + modelProperty = this.getHasMany( + targetModel, + relationName, + isDefaultForeignKey, + foreignKey, + ); relationUtils.addPropertyToModel(sourceClass, modelProperty); relationUtils.addRequiredImports( @@ -114,9 +118,16 @@ module.exports = class RelationHasMany extends RelationGenerator { } getHasMany(className, relationName, isDefaultForeignKey, foreignKey) { - let relationDecoretor = [{ name: 'hasMany', arguments: ['() => ' + className + " ,{keyTo: '" + foreignKey + "'}"] }] + let relationDecoretor = [ + { + name: 'hasMany', + arguments: ['() => ' + className + " ,{keyTo: '" + foreignKey + "'}"], + }, + ]; if (isDefaultForeignKey) { - relationDecoretor = [{ name: 'hasMany', arguments: ['() => ' + className] }] + relationDecoretor = [ + {name: 'hasMany', arguments: ['() => ' + className]}, + ]; } let relationProperty = { decorators: relationDecoretor, diff --git a/packages/cli/generators/relation/relationutils.js b/packages/cli/generators/relation/relationutils.js index 4284950ad96d..d75e9e85f695 100644 --- a/packages/cli/generators/relation/relationutils.js +++ b/packages/cli/generators/relation/relationutils.js @@ -8,27 +8,27 @@ exports.relationType = { hasOne: 'hasOne', }; -exports.addFileToProject = function (project, path, modelName) { +exports.addFileToProject = function(project, path, modelName) { const fileName = path + '/' + modelName + '.model.ts'; return project.addExistingSourceFile(fileName); }; -exports.getClassObj = function (fileName, modelName) { +exports.getClassObj = function(fileName, modelName) { return fileName.getClassOrThrow(modelName); }; -exports.getClassesCount = function (fileName) { +exports.getClassesCount = function(fileName) { return fileName.getClasses().length; }; -exports.isPropertyExist = function (classObj, propertyName) { +exports.isPropertyExist = function(classObj, propertyName) { return classObj .getProperties() .map(x => x.getName()) .includes(propertyName); }; -exports.isRelationExist = function (classObj, propertyName) { +exports.isRelationExist = function(classObj, propertyName) { if (this.isPropertyExist(classObj, propertyName)) { console.log('property ' + propertyName + ' exsist in the model'); throw new Error(' Property exsists'); @@ -36,7 +36,7 @@ exports.isRelationExist = function (classObj, propertyName) { return; }; -exports.vlidateType = function (classObj, foriegnKeyName, foriegnKeyType) { +exports.vlidateType = function(classObj, foriegnKeyName, foriegnKeyType) { if ( utils.lowerCase( classObj @@ -51,7 +51,7 @@ exports.vlidateType = function (classObj, foriegnKeyName, foriegnKeyType) { return; }; -exports.addForeginKey = function (foreginKey, sourceModelPrimaryKeyType) { +exports.addForeginKey = function(foreginKey, sourceModelPrimaryKeyType) { let fkProperty = { decorators: [ { @@ -65,23 +65,23 @@ exports.addForeginKey = function (foreginKey, sourceModelPrimaryKeyType) { return fkProperty; }; -exports.addPropertyToModel = function (classOBj, modelProperty) { +exports.addPropertyToModel = function(classOBj, modelProperty) { classOBj.insertProperty(this.getPropertiesCount(classOBj), modelProperty); classOBj.insertText(this.getPropertyStartPos(classOBj), '\n'); }; -exports.getPropertiesCount = function (classObj) { +exports.getPropertiesCount = function(classObj) { return classObj.getProperties().length; }; -exports.getPropertyStartPos = function (classObj) { +exports.getPropertyStartPos = function(classObj) { return classObj .getChildSyntaxList() .getChildAtIndex(this.getPropertiesCount(classObj) - 1) .getPos(); }; -exports.addRequiredImports = function ( +exports.addRequiredImports = function( sourceFile, targetModel, relationType, @@ -98,7 +98,7 @@ exports.addRequiredImports = function ( } }; -exports.getRequiredImports = function ( +exports.getRequiredImports = function( targetModel, relationType, targetClassName, @@ -117,7 +117,7 @@ exports.getRequiredImports = function ( return importsArray; }; -exports.addCurrentImport = function (sourceFile, currentImport) { +exports.addCurrentImport = function(sourceFile, currentImport) { if (!this.doesModuleExists(sourceFile, currentImport.module)) { sourceFile.addImportDeclaration({ moduleSpecifier: currentImport.module, @@ -130,11 +130,11 @@ exports.addCurrentImport = function (sourceFile, currentImport) { } }; -exports.doesModuleExists = function (sourceFile, moduleName) { +exports.doesModuleExists = function(sourceFile, moduleName) { return sourceFile.getImportDeclaration(moduleName); }; -exports.doesImportExistInModule = function (sourceFile, currentImport) { +exports.doesImportExistInModule = function(sourceFile, currentImport) { let identicalImport; let relevantImports = this.getNamedImportsFromModule( sourceFile, @@ -149,7 +149,7 @@ exports.doesImportExistInModule = function (sourceFile, currentImport) { return identicalImport && identicalImport.length > 0; }; -exports.getNamedImportsFromModule = function (sourceFile, moduleName) { +exports.getNamedImportsFromModule = function(sourceFile, moduleName) { let allImports = sourceFile.getImportDeclarations(); let relevantImports = allImports.filter( imp => imp.getModuleSpecifierValue() == moduleName, From fcb260de508f72e72ae5f71296f549666da92b12 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 00:10:18 -0800 Subject: [PATCH 77/89] Removed scaffold function from RelationGenerator class. --- packages/cli/generators/relation/relation.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/cli/generators/relation/relation.js b/packages/cli/generators/relation/relation.js index 61d186e26e29..bc1a8420051c 100644 --- a/packages/cli/generators/relation/relation.js +++ b/packages/cli/generators/relation/relation.js @@ -30,14 +30,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); } - scaffold() { - // We don't want to call the base scaffold function since it copies - // all of the templates! - // we can set here additional specific this.artifactInfo.xxx parameters if needed - - return; - } - generateAll(options) { this.generateControllers(options); this.generateModels(options); From deca37a5f1186d0ec628c1e12a72d7eaff33a39d Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 00:20:47 -0800 Subject: [PATCH 78/89] Fixed debug string. --- packages/cli/generators/relation/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index a09f38eed778..3cef432897e2 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -436,7 +436,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { "'sourceModel' and 'destinationModel' parameter values should be different.", ); } - debug('Invoke Controller generator...'); + debug('Invoke generator...'); var relation; From f1140e8ea53277b633bc6a97825f71d3a09ec720 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 01:11:43 -0800 Subject: [PATCH 79/89] Fixed error Incorrect Relation Type in relation/index.js:416:23. --- packages/cli/generators/relation/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 3cef432897e2..0fb1ae04df73 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -172,19 +172,19 @@ module.exports = class RelationGenerator extends ArtifactGenerator { _getDefaultRelationName() { var defaultRelationName; - switch (this.options.relationType) { + switch (this.artifactInfo.relationType) { case relationUtils.relationType.belongsTo: defaultRelationName = - utils.camelCase(this.options.destinationModel) + - utils.toClassName(this.options.sourceModelPrimaryKey); + utils.camelCase(this.artifactInfo.destinationModel) + + utils.toClassName(this.artifactInfo.sourceModelPrimaryKey); break; case relationUtils.relationType.hasMany: defaultRelationName = utils.pluralize( - utils.camelCase(this.options.destinationModel), + utils.camelCase(this.artifactInfo.destinationModel), ); break; case relationUtils.relationType.hasOne: - defaultRelationName = utils.camelCase(this.options.destinationModel); + defaultRelationName = utils.camelCase(this.artifactInfo.destinationModel); break; default: throw new Error(ERROR_INCORRECT_RELATION_TYPE); From b34bd6cd70dd3a24a3cff9a82a982dcadc68672e Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 03:45:24 -0800 Subject: [PATCH 80/89] removed validation sourceModel and destinationModel is the same. --- packages/cli/generators/relation/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 0fb1ae04df73..1211023b2048 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -431,11 +431,6 @@ module.exports = class RelationGenerator extends ArtifactGenerator { if (!this.artifactInfo.relationType) { throw new Error("'relationType' parameters should be specified."); } - if (this.artifactInfo.sourceModel === this.artifactInfo.destinationModel) { - throw new Error( - "'sourceModel' and 'destinationModel' parameter values should be different.", - ); - } debug('Invoke generator...'); var relation; From ada357eff655403bea858a188f32417c5668d491 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 03:51:13 -0800 Subject: [PATCH 81/89] Code refactoring. --- packages/cli/generators/relation/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 1211023b2048..ee18fd8b8bfc 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -26,6 +26,8 @@ const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; const ERROR_NO_MODELS_FOUND = 'Model was not found in'; const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; +const ERROR_RELATION_TYPE_PARAMETER_SHOULD_BE_SPECIFIED = + "'relationType' parameter should be specified."; const ERROR_SOURCE_MODEL_PRIMARY_KEY_DOES_NOT_EXIST = 'Source model primary key does not exist.'; @@ -429,7 +431,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { scaffold() { if (!this.artifactInfo.relationType) { - throw new Error("'relationType' parameters should be specified."); + throw new Error(ERROR_RELATION_TYPE_PARAMETER_SHOULD_BE_SPECIFIED); } debug('Invoke generator...'); From bc14a4173505ab74836c812177b7f7d9e838b164 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 03:55:25 -0800 Subject: [PATCH 82/89] Code refactoring. --- packages/cli/generators/relation/index.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index ee18fd8b8bfc..c0b292d60b10 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -83,17 +83,11 @@ module.exports = class RelationGenerator extends ArtifactGenerator { * @param {string} modelName */ async _getModelPrimaryKeyProperty(modelName) { - let fileContent = ''; - let modelFile = path.join( + const modelFile = path.join( this.artifactInfo.modelDir, utils.getModelFileName(modelName), ); - try { - fileContent = this.fs.read(modelFile, {}); - } catch (err) { - //debug(`${ERROR_READING_FILE} ${modelFile}: ${err.message}`); - return this.exit(err); - } + const fileContent = this.fs.read(modelFile, {}); return tsquery.getIdFromModel(fileContent); } From 7e824c72e4510498c45edf7f1794d54973cee614 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 04:38:27 -0800 Subject: [PATCH 83/89] Added validation is model exist. --- packages/cli/generators/relation/index.js | 30 ++++++----------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index c0b292d60b10..ebaad0fa33ca 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -23,8 +23,9 @@ const RelationHasOne = require('./relationHasOne'); const RepositoryRelation = require('./repositoryRelation'); const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; +const ERROR_MODEL_DOES_NOT_EXIST = 'model does not exist.'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; -const ERROR_NO_MODELS_FOUND = 'Model was not found in'; +const ERROR_NO_MODELS_FOUND = 'No models found in'; const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; const ERROR_RELATION_TYPE_PARAMETER_SHOULD_BE_SPECIFIED = "'relationType' parameter should be specified."; @@ -201,37 +202,20 @@ module.exports = class RelationGenerator extends ArtifactGenerator { return this.exit(err); } - if (this.options[parameter]) { - debug( - `Model name received from command line: ${this.options[parameter]}`, - ); - - this.options.model = utils.toClassName(this.options[parameter]); - // assign the model name from the command line only if it is valid - if ( - modelList && - modelList.length > 0 && - modelList.includes(this.options.model) - ) { - Object.assign(this.artifactInfo, { - modelNameList: [this.options[parameter]], - }); - } else { - modelList = []; - } - } if (modelList.length === 0) { - return this.exit( - new Error( + throw new Error( `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', )}`, - ), ); } if (this.options[parameter]) { + if (!modelList.includes(this.options[parameter])) { + throw new Error(`"${this.options[parameter]}" ${ERROR_MODEL_DOES_NOT_EXIST}`); + } + debug( `${parameter} received from command line: ${this.options[parameter]}`, ); From ff54feb847c5e31d99a8cc807235401c0b150ae8 Mon Sep 17 00:00:00 2001 From: Mordi Ifrach Date: Tue, 19 Feb 2019 15:13:59 +0200 Subject: [PATCH 84/89] feat(cli): test for model relation --- packages/cli/test/fixtures/relation/index.js | 97 ++++ .../relation/models/customer.model.ts | 20 + .../fixtures/relation/models/order.model.ts | 20 + .../generators/model.relation.integration.js | 491 ++++++++++++++++++ 4 files changed, 628 insertions(+) create mode 100644 packages/cli/test/fixtures/relation/index.js create mode 100644 packages/cli/test/fixtures/relation/models/customer.model.ts create mode 100644 packages/cli/test/fixtures/relation/models/order.model.ts create mode 100644 packages/cli/test/integration/generators/model.relation.integration.js diff --git a/packages/cli/test/fixtures/relation/index.js b/packages/cli/test/fixtures/relation/index.js new file mode 100644 index 000000000000..f921a2c05288 --- /dev/null +++ b/packages/cli/test/fixtures/relation/index.js @@ -0,0 +1,97 @@ +const DATASOURCE_APP_PATH = 'src/datasources'; +const MODEL_APP_PATH = 'src/models'; +const REPOSITORY_APP_PATH = 'src/repositories'; +const CONFIG_PATH = '.'; +const DUMMY_CONTENT = '--DUMMY VALUE--'; +const fs = require('fs'); + +exports.SANDBOX_FILES = [ + { + path: CONFIG_PATH, + file: 'myconfig.json', + content: JSON.stringify({ + datasource: 'dbmem', + model: 'decoratordefined', + }), + }, + { + path: DATASOURCE_APP_PATH, + file: 'dbkv.datasource.json', + content: JSON.stringify({ + name: 'dbkv', + connector: 'kv-redis', + }), + }, + { + path: DATASOURCE_APP_PATH, + file: 'dbkv.datasource.ts', + content: DUMMY_CONTENT, + }, + { + path: DATASOURCE_APP_PATH, + file: 'dbmem.datasource.json', + content: JSON.stringify({ + name: 'dbmem', + connector: 'memory', + }), + }, + { + path: DATASOURCE_APP_PATH, + file: 'my-ds.datasource.json', + content: JSON.stringify({ + name: 'MyDS', + connector: 'memory', + }), + }, + { + path: DATASOURCE_APP_PATH, + file: 'dbmem.datasource.ts', + content: DUMMY_CONTENT, + }, + { + path: DATASOURCE_APP_PATH, + file: 'restdb.datasource.json', + content: JSON.stringify({ + name: 'restdb', + connector: 'rest', + }), + }, + { + path: DATASOURCE_APP_PATH, + file: 'sqlite-3.datasource.json', + content: JSON.stringify({ + name: 'sqlite3', + connector: 'loopback-connector-sqlite3', + }), + }, + { + path: DATASOURCE_APP_PATH, + file: 'sqlite-3.datasource.ts', + content: DUMMY_CONTENT, + }, + { + path: DATASOURCE_APP_PATH, + file: 'restdb.datasource.ts', + content: DUMMY_CONTENT, + }, + { + path: MODEL_APP_PATH, + file: 'customer.model.ts', + content: fs.readFileSync( + require.resolve('./models/customer.model.ts'), + { + encoding: 'utf-8', + }, + ), + }, + { + path: MODEL_APP_PATH, + file: 'order.model.ts', + content: fs.readFileSync( + require.resolve('./models/order.model.ts'), + { + encoding: 'utf-8', + }, + ), + }, +]; diff --git a/packages/cli/test/fixtures/relation/models/customer.model.ts b/packages/cli/test/fixtures/relation/models/customer.model.ts new file mode 100644 index 000000000000..b562ca8e8ce8 --- /dev/null +++ b/packages/cli/test/fixtures/relation/models/customer.model.ts @@ -0,0 +1,20 @@ +import { Entity, model, property } from '@loopback/repository'; + +@model() +export class Customer extends Entity { + @property({ + type: 'number', + id: true, + default: 0, + }) + id?: number; + + @property({ + type: 'string', + }) + name?: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/packages/cli/test/fixtures/relation/models/order.model.ts b/packages/cli/test/fixtures/relation/models/order.model.ts new file mode 100644 index 000000000000..d3d8fdc81a64 --- /dev/null +++ b/packages/cli/test/fixtures/relation/models/order.model.ts @@ -0,0 +1,20 @@ +import { Entity, model, property } from '@loopback/repository'; + +@model() +export class Order extends Entity { + @property({ + type: 'number', + id: true, + default: 0, + }) + id?: number; + + @property({ + type: 'string', + }) + name?: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/packages/cli/test/integration/generators/model.relation.integration.js b/packages/cli/test/integration/generators/model.relation.integration.js new file mode 100644 index 000000000000..b1053180ceb8 --- /dev/null +++ b/packages/cli/test/integration/generators/model.relation.integration.js @@ -0,0 +1,491 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: @loopback/cli +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const path = require('path'); +const assert = require('yeoman-assert'); +const testlab = require('@loopback/testlab'); +const fs = require('fs'); + +const expect = testlab.expect; +const TestSandbox = testlab.TestSandbox; + +const generator = path.join(__dirname, '../../../generators/relation'); +const SANDBOX_FILES = require('../../fixtures/relation').SANDBOX_FILES; +const testUtils = require('../../test-utils'); + +// Test Sandbox +const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); +const sandbox = new TestSandbox(SANDBOX_PATH); + +describe('lb4 relation', function () { + // tslint:disable-next-line:no-invalid-this + this.timeout(30000); + + beforeEach('reset sandbox', async () => { + await sandbox.reset(); + }); + + // special cases regardless of the repository type + describe('generate model relation', () => { + it('generates lb4 relation', async () => { + const multiItemPrompt = { + relationType: 'hasMany', + sourceModel: 'Customer', + destinationModel: 'Order', + relationName: 'orders', + }; + + await testUtils + .executeGenerator(generator) + .inDir(SANDBOX_PATH, () => + testUtils.givenLBProject(SANDBOX_PATH, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withPrompts(multiItemPrompt); + // const expectedMultiWordFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'multi-word.repository.ts', + // ); + // const expectedDefaultModelFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'defaultmodel.repository.ts', + // ); + + // assert.file(expectedMultiWordFile); + // assert.file(expectedDefaultModelFile); + + // assert.fileContent( + // expectedMultiWordFile, + // /export class MultiWordRepository extends DefaultCrudRepository { + // const multiItemPrompt = { + // dataSourceClass: 'DbmemDatasource', + // modelNameList: ['MultiWord'], + // }; + + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(multiItemPrompt); + + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'multi-word.repository.ts', + // ); + + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /export class MultiWordRepository extends DefaultCrudRepository { + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withArguments('myrepo --datasource dbmem --model MultiWord'); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'myrepo.repository.ts', + // ); + + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /export class MyrepoRepository extends DefaultCrudRepository { + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withArguments('--config myconfig.json'); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'decoratordefined.repository.ts', + // ); + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /export class DecoratordefinedRepository extends DefaultCrudRepository\ { + // const multiItemPrompt = { + // dataSourceClass: 'DbmemDatasource', + // modelNameList: ['InvalidId'], + // propertyName: 'myid', + // }; + + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(multiItemPrompt); + + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'invalid-id.repository.ts', + // ); + + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /export class InvalidIdRepository extends DefaultCrudRepository { + // it('does not run with an invalid model name', async () => { + // const basicPrompt = { + // dataSourceClass: 'DbmemDatasource', + // }; + // return expect( + // testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(basicPrompt) + // .withArguments(' --model InvalidModel'), + // ).to.be.rejectedWith(/No models found/); + // }); + + // it("does not run when user doesn't select a model", async () => { + // const basicPrompt = { + // dataSourceClass: 'DbmemDatasource', + // }; + // return expect( + // testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(basicPrompt), + // ).to.be.rejectedWith(/You did not select a valid model/); + // }); + + // it('does not run with empty datasource list', async () => { + // return expect( + // testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => testUtils.givenLBProject(SANDBOX_PATH)), + // ).to.be.rejectedWith(/No datasources found/); + // }); + // }); + + // describe('valid generation of crud repositories', () => { + // it('generates a crud repository from default model', async () => { + // const basicPrompt = { + // dataSourceClass: 'DbmemDatasource', + // }; + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(basicPrompt) + // .withArguments(' --model Defaultmodel'); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'defaultmodel.repository.ts', + // ); + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /export class DefaultmodelRepository extends DefaultCrudRepository\ { + // const files = SANDBOX_FILES.filter( + // e => + // e.path !== 'src/datasources' || + // e.file.includes('sqlite-3.datasource.'), + // ); + // const basicPrompt = { + // dataSourceClass: 'Sqlite_3Datasource', + // }; + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // // Only use the sqlite3 datasource + // additionalFiles: files, + // }), + // ) + // .withPrompts(basicPrompt) + // .withArguments(' --model Defaultmodel'); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'defaultmodel.repository.ts', + // ); + // assert.file(expectedFile); + // }); + + // it('generates a crud repository from hyphened model file name', async () => { + // const basicPrompt = { + // dataSourceClass: 'MyDsDatasource', + // }; + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(basicPrompt) + // .withArguments(' --model Defaultmodel'); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'defaultmodel.repository.ts', + // ); + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /import {MyDSDataSource} from '..\/datasources';/, + // ); + // assert.fileContent( + // expectedFile, + // /\@inject\('datasources.MyDS'\) dataSource: MyDSDataSource,/, + // ); + // assert.fileContent( + // expectedFile, + // /export class DefaultmodelRepository extends DefaultCrudRepository\ { + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withArguments('--datasource dbmem --model decoratordefined'); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'decoratordefined.repository.ts', + // ); + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /export class DecoratordefinedRepository extends DefaultCrudRepository\ { + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withArguments( + // '--datasource dbmem --model decoratordefined --repositoryBaseClass DefaultmodelRepository', + // ); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'decoratordefined.repository.ts', + // ); + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /import {DefaultmodelRepository} from '.\/defaultmodel.repository.base';/, + // ); + // assert.fileContent( + // expectedFile, + // /export class DecoratordefinedRepository extends DefaultmodelRepository\ { + // it('generates a kv repository from default model', async () => { + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withArguments( + // '--datasource dbkv --model Defaultmodel --repositoryBaseClass DefaultKeyValueRepository', + // ); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'defaultmodel.repository.ts', + // ); + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /DefaultmodelRepository extends DefaultKeyValueRepository { + // const basicPrompt = { + // dataSourceClass: 'DbkvDatasource', + // }; + // await testUtils + // .executeGenerator(generator) + // .inDir(SANDBOX_PATH, () => + // testUtils.givenLBProject(SANDBOX_PATH, { + // additionalFiles: SANDBOX_FILES, + // }), + // ) + // .withPrompts(basicPrompt) + // .withArguments( + // '--model decoratordefined --repositoryBaseClass DefaultKeyValueRepository', + // ); + // const expectedFile = path.join( + // SANDBOX_PATH, + // REPOSITORY_APP_PATH, + // 'decoratordefined.repository.ts', + // ); + + // assert.file(expectedFile); + // assert.fileContent( + // expectedFile, + // /DecoratordefinedRepository extends DefaultKeyValueRepository Date: Tue, 19 Feb 2019 05:43:53 -0800 Subject: [PATCH 85/89] npm run lint:fix --- packages/cli/generators/relation/index.js | 14 +- packages/cli/test/fixtures/relation/index.js | 18 +- .../relation/models/customer.model.ts | 2 +- .../fixtures/relation/models/order.model.ts | 2 +- .../generators/model.relation.integration.js | 453 +----------------- 5 files changed, 34 insertions(+), 455 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index ebaad0fa33ca..9c09588ad73f 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -27,7 +27,7 @@ const ERROR_MODEL_DOES_NOT_EXIST = 'model does not exist.'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; const ERROR_NO_MODELS_FOUND = 'No models found in'; const ERROR_NO_SOURCE_MODEL_SELECTED = 'No source model selected'; -const ERROR_RELATION_TYPE_PARAMETER_SHOULD_BE_SPECIFIED = +const ERROR_RELATION_TYPE_PARAMETER_SHOULD_BE_SPECIFIED = "'relationType' parameter should be specified."; const ERROR_SOURCE_MODEL_PRIMARY_KEY_DOES_NOT_EXIST = 'Source model primary key does not exist.'; @@ -181,7 +181,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); break; case relationUtils.relationType.hasOne: - defaultRelationName = utils.camelCase(this.artifactInfo.destinationModel); + defaultRelationName = utils.camelCase( + this.artifactInfo.destinationModel, + ); break; default: throw new Error(ERROR_INCORRECT_RELATION_TYPE); @@ -203,8 +205,8 @@ module.exports = class RelationGenerator extends ArtifactGenerator { } if (modelList.length === 0) { - throw new Error( - `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. + throw new Error( + `${ERROR_NO_MODELS_FOUND} ${this.artifactInfo.modelDir}. ${chalk.yellow( 'Please visit https://loopback.io/doc/en/lb4/Model-generator.html for information on how models are discovered', )}`, @@ -213,7 +215,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { if (this.options[parameter]) { if (!modelList.includes(this.options[parameter])) { - throw new Error(`"${this.options[parameter]}" ${ERROR_MODEL_DOES_NOT_EXIST}`); + throw new Error( + `"${this.options[parameter]}" ${ERROR_MODEL_DOES_NOT_EXIST}`, + ); } debug( diff --git a/packages/cli/test/fixtures/relation/index.js b/packages/cli/test/fixtures/relation/index.js index f921a2c05288..8106aca04bc3 100644 --- a/packages/cli/test/fixtures/relation/index.js +++ b/packages/cli/test/fixtures/relation/index.js @@ -77,21 +77,15 @@ exports.SANDBOX_FILES = [ { path: MODEL_APP_PATH, file: 'customer.model.ts', - content: fs.readFileSync( - require.resolve('./models/customer.model.ts'), - { - encoding: 'utf-8', - }, - ), + content: fs.readFileSync(require.resolve('./models/customer.model.ts'), { + encoding: 'utf-8', + }), }, { path: MODEL_APP_PATH, file: 'order.model.ts', - content: fs.readFileSync( - require.resolve('./models/order.model.ts'), - { - encoding: 'utf-8', - }, - ), + content: fs.readFileSync(require.resolve('./models/order.model.ts'), { + encoding: 'utf-8', + }), }, ]; diff --git a/packages/cli/test/fixtures/relation/models/customer.model.ts b/packages/cli/test/fixtures/relation/models/customer.model.ts index b562ca8e8ce8..06d0fc93ca4f 100644 --- a/packages/cli/test/fixtures/relation/models/customer.model.ts +++ b/packages/cli/test/fixtures/relation/models/customer.model.ts @@ -1,4 +1,4 @@ -import { Entity, model, property } from '@loopback/repository'; +import {Entity, model, property} from '@loopback/repository'; @model() export class Customer extends Entity { diff --git a/packages/cli/test/fixtures/relation/models/order.model.ts b/packages/cli/test/fixtures/relation/models/order.model.ts index d3d8fdc81a64..84f947dc2eb0 100644 --- a/packages/cli/test/fixtures/relation/models/order.model.ts +++ b/packages/cli/test/fixtures/relation/models/order.model.ts @@ -1,4 +1,4 @@ -import { Entity, model, property } from '@loopback/repository'; +import {Entity, model, property} from '@loopback/repository'; @model() export class Order extends Entity { diff --git a/packages/cli/test/integration/generators/model.relation.integration.js b/packages/cli/test/integration/generators/model.relation.integration.js index b1053180ceb8..76248bba3b65 100644 --- a/packages/cli/test/integration/generators/model.relation.integration.js +++ b/packages/cli/test/integration/generators/model.relation.integration.js @@ -19,9 +19,10 @@ const testUtils = require('../../test-utils'); // Test Sandbox const SANDBOX_PATH = path.resolve(__dirname, '..', '.sandbox'); +const MODEL_APP_PATH = 'src/models'; const sandbox = new TestSandbox(SANDBOX_PATH); -describe('lb4 relation', function () { +describe('lb4 relation', function() { // tslint:disable-next-line:no-invalid-this this.timeout(30000); @@ -47,445 +48,25 @@ describe('lb4 relation', function () { }), ) .withPrompts(multiItemPrompt); - // const expectedMultiWordFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'multi-word.repository.ts', - // ); - // const expectedDefaultModelFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'defaultmodel.repository.ts', - // ); - // assert.file(expectedMultiWordFile); - // assert.file(expectedDefaultModelFile); + const expectedFile = path.join( + SANDBOX_PATH, + MODEL_APP_PATH, + 'customer.model.ts', + ); - // assert.fileContent( - // expectedMultiWordFile, - // /export class MultiWordRepository extends DefaultCrudRepository Order, \{ keyTo: 'customerId' \}\)/, + ); - // assert.file(INDEX_FILE); - // assert.fileContent( - // INDEX_FILE, - // /export \* from '.\/multi-word.repository';/, - // ); - // assert.fileContent( - // INDEX_FILE, - // /export \* from '.\/defaultmodel.repository';/, - // ); - // }); - - // it('generates a multi-word crud repository', async () => { - // const multiItemPrompt = { - // dataSourceClass: 'DbmemDatasource', - // modelNameList: ['MultiWord'], - // }; - - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(multiItemPrompt); - - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'multi-word.repository.ts', - // ); - - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /export class MultiWordRepository extends DefaultCrudRepository { - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withArguments('myrepo --datasource dbmem --model MultiWord'); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'myrepo.repository.ts', - // ); - - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /export class MyrepoRepository extends DefaultCrudRepository { - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withArguments('--config myconfig.json'); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'decoratordefined.repository.ts', - // ); - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /export class DecoratordefinedRepository extends DefaultCrudRepository\ { - // const multiItemPrompt = { - // dataSourceClass: 'DbmemDatasource', - // modelNameList: ['InvalidId'], - // propertyName: 'myid', - // }; - - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(multiItemPrompt); - - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'invalid-id.repository.ts', - // ); - - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /export class InvalidIdRepository extends DefaultCrudRepository { - // it('does not run with an invalid model name', async () => { - // const basicPrompt = { - // dataSourceClass: 'DbmemDatasource', - // }; - // return expect( - // testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(basicPrompt) - // .withArguments(' --model InvalidModel'), - // ).to.be.rejectedWith(/No models found/); - // }); - - // it("does not run when user doesn't select a model", async () => { - // const basicPrompt = { - // dataSourceClass: 'DbmemDatasource', - // }; - // return expect( - // testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(basicPrompt), - // ).to.be.rejectedWith(/You did not select a valid model/); - // }); - - // it('does not run with empty datasource list', async () => { - // return expect( - // testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => testUtils.givenLBProject(SANDBOX_PATH)), - // ).to.be.rejectedWith(/No datasources found/); - // }); - // }); - - // describe('valid generation of crud repositories', () => { - // it('generates a crud repository from default model', async () => { - // const basicPrompt = { - // dataSourceClass: 'DbmemDatasource', - // }; - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(basicPrompt) - // .withArguments(' --model Defaultmodel'); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'defaultmodel.repository.ts', - // ); - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /export class DefaultmodelRepository extends DefaultCrudRepository\ { - // const files = SANDBOX_FILES.filter( - // e => - // e.path !== 'src/datasources' || - // e.file.includes('sqlite-3.datasource.'), - // ); - // const basicPrompt = { - // dataSourceClass: 'Sqlite_3Datasource', - // }; - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // // Only use the sqlite3 datasource - // additionalFiles: files, - // }), - // ) - // .withPrompts(basicPrompt) - // .withArguments(' --model Defaultmodel'); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'defaultmodel.repository.ts', - // ); - // assert.file(expectedFile); - // }); - - // it('generates a crud repository from hyphened model file name', async () => { - // const basicPrompt = { - // dataSourceClass: 'MyDsDatasource', - // }; - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(basicPrompt) - // .withArguments(' --model Defaultmodel'); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'defaultmodel.repository.ts', - // ); - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /import {MyDSDataSource} from '..\/datasources';/, - // ); - // assert.fileContent( - // expectedFile, - // /\@inject\('datasources.MyDS'\) dataSource: MyDSDataSource,/, - // ); - // assert.fileContent( - // expectedFile, - // /export class DefaultmodelRepository extends DefaultCrudRepository\ { - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withArguments('--datasource dbmem --model decoratordefined'); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'decoratordefined.repository.ts', - // ); - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /export class DecoratordefinedRepository extends DefaultCrudRepository\ { - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withArguments( - // '--datasource dbmem --model decoratordefined --repositoryBaseClass DefaultmodelRepository', - // ); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'decoratordefined.repository.ts', - // ); - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /import {DefaultmodelRepository} from '.\/defaultmodel.repository.base';/, - // ); - // assert.fileContent( - // expectedFile, - // /export class DecoratordefinedRepository extends DefaultmodelRepository\ { - // it('generates a kv repository from default model', async () => { - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withArguments( - // '--datasource dbkv --model Defaultmodel --repositoryBaseClass DefaultKeyValueRepository', - // ); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'defaultmodel.repository.ts', - // ); - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /DefaultmodelRepository extends DefaultKeyValueRepository { - // const basicPrompt = { - // dataSourceClass: 'DbkvDatasource', - // }; - // await testUtils - // .executeGenerator(generator) - // .inDir(SANDBOX_PATH, () => - // testUtils.givenLBProject(SANDBOX_PATH, { - // additionalFiles: SANDBOX_FILES, - // }), - // ) - // .withPrompts(basicPrompt) - // .withArguments( - // '--model decoratordefined --repositoryBaseClass DefaultKeyValueRepository', - // ); - // const expectedFile = path.join( - // SANDBOX_PATH, - // REPOSITORY_APP_PATH, - // 'decoratordefined.repository.ts', - // ); - - // assert.file(expectedFile); - // assert.fileContent( - // expectedFile, - // /DecoratordefinedRepository extends DefaultKeyValueRepository Date: Tue, 19 Feb 2019 06:07:39 -0800 Subject: [PATCH 86/89] Removed repositoryRelation.js. --- packages/cli/generators/relation/index.js | 2 - .../generators/relation/repositoryRelation.js | 332 ------------------ 2 files changed, 334 deletions(-) delete mode 100644 packages/cli/generators/relation/repositoryRelation.js diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 9c09588ad73f..bb42f2251dcc 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -20,8 +20,6 @@ const RelationBelongsTo = require('./relationBelongsTo'); const RelationHasMany = require('./relationHasMany'); const RelationHasOne = require('./relationHasOne'); -const RepositoryRelation = require('./repositoryRelation'); - const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect Relation Type'; const ERROR_MODEL_DOES_NOT_EXIST = 'model does not exist.'; const ERROR_NO_DESTINATION_MODEL_SELECTED = 'No destination model selected'; diff --git a/packages/cli/generators/relation/repositoryRelation.js b/packages/cli/generators/relation/repositoryRelation.js deleted file mode 100644 index 25c4d6de0b9d..000000000000 --- a/packages/cli/generators/relation/repositoryRelation.js +++ /dev/null @@ -1,332 +0,0 @@ -const ArtifactGenerator = require('../../lib/artifact-generator'); - -const ast = require('ts-simple-ast'); -const path = require('path'); -const utils = require('../../lib/utils'); -const relationUtils = require('./relationutils'); - -module.exports = class RepositoryRelation extends ArtifactGenerator { - constructor(args, opts) { - super(args, opts); - } - - _setupGenerator() { - super._setupGenerator(); - - this.artifactInfo = { - type: 'relation', - rootDir: utils.sourceRootDir, - }; - this.artifactInfo.repositoriesDir = path.resolve( - this.artifactInfo.rootDir, - 'repositories', - ); - this.artifactInfo.modelsDir = path.resolve( - this.artifactInfo.rootDir, - 'models', - ); - } - - generateRelationRepository( - sourceModel, - targetModel, - foreignKey, - relationName, - ) { - this.initializeProperties(sourceModel, targetModel, relationName); - this.handleImports(); - this.handleProperties(); - this.handleConstructor(); - this.artifactInfo.srcRepositoryFile.save(); - } - - initializeProperties(sourceModel, targetModel, relationName) { - this.artifactInfo.srcModelFile = path.resolve( - this.artifactInfo.modelsDir, - sourceModel + '.model.ts', - ); - - this.artifactInfo.dstModelFile = path.resolve( - this.artifactInfo.modelsDir, - targetModel + '.model.ts', - ); - - this.artifactInfo.srcModelClass = this.getClassName( - this.artifactInfo.srcModelFile, - ); - - this.artifactInfo.dstModelClass = this.getClassName( - this.artifactInfo.dstModelFile, - ); - - this.artifactInfo.srcRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - sourceModel + '.repository.ts', - ); - - this.artifactInfo.dstRepositoryFile = path.resolve( - this.artifactInfo.repositoriesDir, - targetModel + '.repository.ts', - ); - - this.artifactInfo.srcRepositoryClassName = this.getClassName( - this.artifactInfo.srcRepositoryFile, - ); - - this.artifactInfo.dstRepositoryClassName = this.getClassName( - this.artifactInfo.dstRepositoryFile, - ); - - this.artifactInfo.relationName = relationName; - - this.artifactInfo.relationProperty = { - name: this.getRelationPropertyName(), - type: this.getRelationPropertyType(), - }; - - this.artifactInfo.srcRepositoryFile = new ast.Project().addExistingSourceFile( - this.artifactInfo.srcRepositoryFile, - ); - } - - getRelationPropertyName() { - let propertyName = this.artifactInfo.dstModelClass[0].toLowerCase(); - propertyName += this.artifactInfo.dstModelClass.substring(1); - - if (this.artifactInfo.relationName == relationUtils.relationType.hasMany) { - propertyName += 's'; - } - return propertyName; - } - - getRelationPropertyType() { - let propertyType = this.capitalizeFirstLetter( - this.artifactInfo.relationName, - ); - if ( - this.artifactInfo.relationName == relationUtils.relationType.belongsTo - ) { - propertyType += 'Accessor'; - } else if ( - this.artifactInfo.relationName == relationType.hasOne || - this.artifactInfo.relationName == relationUtils.relationType.hasMany - ) { - propertyType += 'RepositoryFactory'; - } else { - throw Error('relation is invalid'); - } - propertyType = - propertyType + - '<' + - this.capitalizeFirstLetter(this.artifactInfo.dstModelClass) + - ', typeof ' + - this.capitalizeFirstLetter(this.artifactInfo.srcModelClass) + - '.prototype.id>'; - - return propertyType; - } - - getClassName(fileName) { - let sourceFile = new ast.Project().addExistingSourceFile(fileName); - let className = sourceFile.getClasses()[0].getNameOrThrow(); - return className; - } - - handleImports() { - let requierdImports = this.getRequiredImports(); - this.addRequiredImports(requierdImports); - } - - getRequiredImports() { - let importsArray = [ - { - name: this.artifactInfo.dstModelClass, - module: '../models', - }, - { - name: 'repository', - module: '@loopback/repository', - }, - { - name: 'Getter', - module: '@loopback/core', - }, - { - name: this.artifactInfo.dstRepositoryClassName, - module: './index', - }, - ]; - - let RelationName = this.capitalizeFirstLetter( - this.artifactInfo.relationName, - ); - switch (this.artifactInfo.relationName) { - case relationType.hasMany: - importsArray.push({ - name: RelationName + 'RepositoryFactory', - module: '@loopback/repository', - }); - break; - case relationType.hasOne: - importsArray.push({ - name: RelationName + 'RepositoryFactory', - module: '@loopback/repository', - }); - break; - - case relationType.belongsTo: - importsArray.push({ - name: RelationName + 'Accessor', - module: '@loopback/repository', - }); - break; - - default: - result = false; - } - - return importsArray; - } - - addRequiredImports(requiredImports) { - for (let currentImport of requiredImports) { - this.addImport(currentImport, this.artifactInfo.srcRepositoryFile); - } - } - - addImport(requiredImport) { - if (!this.doesModuleExist(requiredImport)) { - this.addImportWithNonExistingModule(requiredImport); - } else { - this.addImportsWithExistingModule(requiredImport); - } - } - - addImportWithNonExistingModule(requiredImport) { - this.artifactInfo.srcRepositoryFile.addImportDeclaration({ - moduleSpecifier: requiredImport.module, - namedImports: [requiredImport.name], - }); - } - - addImportsWithExistingModule(requiredImport) { - let moduleName = requiredImport.module; - let importDeclcaration = this.artifactInfo.srcRepositoryFile.getImportDeclarationOrThrow( - moduleName, - ); - if (!this.doesImportExist(importDeclcaration, requiredImport.name)) { - importDeclcaration.addNamedImport(requiredImport.name); - } - } - - doesImportExist(importDelcaration, importName) { - let allNamedImports = importDelcaration.getNamedImports(); - for (let currentNamedImport of allNamedImports) { - if (currentNamedImport.getName() == importName) { - return true; - } - } - return false; - } - - doesModuleExist(importDeclaration) { - let moduleName = importDeclaration.module; - let relevantImport = this.artifactInfo.srcRepositoryFile.getImportDeclaration( - moduleName, - ); - return relevantImport != undefined; - } - - handleProperties() { - let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( - this.artifactInfo.srcRepositoryClassName, - ); - - this.addProperty(classDeclaration); - - this.orderProperties(classDeclaration); - } - - addProperty(classDeclaration) { - classDeclaration.addProperty({ - scope: ast.Scope.Public, - isReadonly: true, - name: this.artifactInfo.relationProperty.name, - type: this.artifactInfo.relationProperty.type, - }); - } - - orderProperties(classDeclaration) { - classDeclaration.getProperties().forEach(function(currentProperty) { - currentProperty.setOrder(0); - }); - } - - handleConstructor() { - let classDeclaration = this.artifactInfo.srcRepositoryFile.getClassOrThrow( - this.artifactInfo.srcRepositoryClassName, - ); - let classConstructor = classDeclaration.getConstructors()[0]; - - this.addParameters(classConstructor); - - this.addCreator(classConstructor); - } - - addParameters(classConstructor) { - classConstructor.addParameter({ - decorators: [ - { - name: 'repository.getter', - arguments: ["'" + this.artifactInfo.dstRepositoryClassName + "'"], - }, - ], - name: - this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - 'Getter', - type: 'Getter<' + this.artifactInfo.dstRepositoryClassName + '>,', - scope: ast.Scope.Protected, - }); - } - - addCreator(classConstructor) { - let statement = - 'this.create' + - this.capitalizeFirstLetter(this.artifactInfo.relationName); - if (this.artifactInfo.relationName == relationType.belongsTo) { - statement += 'Accessor'; - } else if ( - this.artifactInfo.relationName == relationType.hasMany || - this.artifactInfo.relationName == relationType.hasOne - ) { - statement += 'RepositoryFactory'; - } else { - throw Error('relation is invalid'); - } - statement += 'For('; - - let parameter1 = "'" + this.artifactInfo.relationProperty.name + "',"; - let paramater2 = - this.regularizeFirstLetter(this.artifactInfo.dstRepositoryClassName) + - 'Getter,'; - - statement = - 'this.' + - this.artifactInfo.relationProperty.name + - '=' + - statement + - parameter1 + - paramater2 + - ');'; - - classConstructor.insertStatements(1, statement); - } - - capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); - } - - regularizeFirstLetter(string) { - return string.charAt(0).toLowerCase() + string.slice(1); - } -}; From d831b91b1bebe9546e009195b26ff5f91b0de9de Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 06:40:06 -0800 Subject: [PATCH 87/89] Added check import hasMany to test. --- .../integration/generators/model.relation.integration.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/cli/test/integration/generators/model.relation.integration.js b/packages/cli/test/integration/generators/model.relation.integration.js index 76248bba3b65..df8af8f96a69 100644 --- a/packages/cli/test/integration/generators/model.relation.integration.js +++ b/packages/cli/test/integration/generators/model.relation.integration.js @@ -58,7 +58,13 @@ describe('lb4 relation', function() { assert.file(expectedFile); assert.fileContent( expectedFile, - /import \{ Order \} from "\.\/order.model";/, + /import \{Entity, model, property, hasMany \} from '@loopback\/repository';/, + ); + + + assert.fileContent( + expectedFile, + /import \{ Order \} from "\.\/order\.model";/, ); assert.fileContent( From 539aa18df3b0b14b0913866fec38a32af64cb655 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 06:59:33 -0800 Subject: [PATCH 88/89] npm run lint:fix --- .../test/integration/generators/model.relation.integration.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/test/integration/generators/model.relation.integration.js b/packages/cli/test/integration/generators/model.relation.integration.js index df8af8f96a69..f851eb08f367 100644 --- a/packages/cli/test/integration/generators/model.relation.integration.js +++ b/packages/cli/test/integration/generators/model.relation.integration.js @@ -61,7 +61,6 @@ describe('lb4 relation', function() { /import \{Entity, model, property, hasMany \} from '@loopback\/repository';/, ); - assert.fileContent( expectedFile, /import \{ Order \} from "\.\/order\.model";/, From c535fa6a457e37d00d406bbc4c11e8c73897b6a8 Mon Sep 17 00:00:00 2001 From: Oleg Gashev Date: Tue, 19 Feb 2019 07:09:47 -0800 Subject: [PATCH 89/89] Added async/await to save module. --- packages/cli/generators/relation/index.js | 4 ++-- packages/cli/generators/relation/relation.js | 4 ++-- packages/cli/generators/relation/relationBelongsTo.js | 4 ++-- packages/cli/generators/relation/relationHasMany.js | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index bb42f2251dcc..f55002919aa3 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -409,7 +409,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { }); } - scaffold() { + async scaffold() { if (!this.artifactInfo.relationType) { throw new Error(ERROR_RELATION_TYPE_PARAMETER_SHOULD_BE_SPECIFIED); } @@ -433,7 +433,7 @@ module.exports = class RelationGenerator extends ArtifactGenerator { throw new Error(ERROR_INCORRECT_RELATION_TYPE); } - relation.generateAll(this.artifactInfo); + await relation.generateAll(this.artifactInfo); } async end() { diff --git a/packages/cli/generators/relation/relation.js b/packages/cli/generators/relation/relation.js index bc1a8420051c..0597ac34d3ab 100644 --- a/packages/cli/generators/relation/relation.js +++ b/packages/cli/generators/relation/relation.js @@ -30,9 +30,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator { ); } - generateAll(options) { + async generateAll(options) { this.generateControllers(options); - this.generateModels(options); + await this.generateModels(options); } generateControllers(options) { diff --git a/packages/cli/generators/relation/relationBelongsTo.js b/packages/cli/generators/relation/relationBelongsTo.js index c8e9a8e7fdff..d610067d96cf 100644 --- a/packages/cli/generators/relation/relationBelongsTo.js +++ b/packages/cli/generators/relation/relationBelongsTo.js @@ -56,7 +56,7 @@ module.exports = class RelationBelongsTo extends RelationGenerator { return; } - generateModels(options) { + async generateModels(options) { let path = this.artifactInfo.modelDir; let sourceModel = options.sourceModel; let targetModel = options.destinationModel; @@ -100,7 +100,7 @@ module.exports = class RelationBelongsTo extends RelationGenerator { targetModel, ); sourceClass.formatText(); - sourceFile.save(); + await sourceFile.save(); } generateRepositories(options) { diff --git a/packages/cli/generators/relation/relationHasMany.js b/packages/cli/generators/relation/relationHasMany.js index e235e7173e19..97e73e748ce6 100644 --- a/packages/cli/generators/relation/relationHasMany.js +++ b/packages/cli/generators/relation/relationHasMany.js @@ -51,7 +51,7 @@ module.exports = class RelationHasMany extends RelationGenerator { return; } - generateModels(options) { + async generateModels(options) { let path = this.artifactInfo.modelDir; let sourceModel = options.sourceModel; let targetModel = options.destinationModel; @@ -93,7 +93,7 @@ module.exports = class RelationHasMany extends RelationGenerator { modelProperty = relationUtils.addForeginKey(foreignKey, fktype); relationUtils.addPropertyToModel(targetClass, modelProperty); targetClass.formatText(); - targetFile.save(); + await targetFile.save(); } modelProperty = this.getHasMany( targetModel, @@ -110,7 +110,7 @@ module.exports = class RelationHasMany extends RelationGenerator { targetModel, ); sourceClass.formatText(); - sourceFile.save(); + await sourceFile.save(); } generateRepositories(options) {