diff --git a/packages/strapi-generate-migrations/lib/before.js b/packages/strapi-generate-migrations/lib/before.js index 4f9d8ea16a..2ed39ac2df 100755 --- a/packages/strapi-generate-migrations/lib/before.js +++ b/packages/strapi-generate-migrations/lib/before.js @@ -76,148 +76,133 @@ module.exports = function (scope, cb) { } }); - let history; - try { - history = JSON.parse(fs.readFileSync(path.resolve(scope.rootPath, 'data', 'migrations', '.history'), 'utf8')); - } catch (err) { - // File not existing - history = {}; - } + const history = () => { + try { + return JSON.parse(fs.readFileSync(path.resolve(scope.rootPath, 'data', 'migrations', '.history'), 'utf8')); + } catch (err) { + // File not existing + return {}; + } + }(); // Register every model. - const migrations = glob.sync(path.resolve(scope.rootPath, 'api', '**', 'models', '*.json')).map((file) => { - let modelName; + const migrations = glob.sync(path.resolve(scope.rootPath, 'api', '**', 'models', '*.json')).map((filepath) => { + try { + const file = JSON.parse(fs.readFileSync(path.resolve(filepath))); - // Only create migration file for the models with the specified connection. - if (JSON.parse(fs.readFileSync(path.resolve(file))).connection === scope.connection) { + // Only create migration file for the models with the specified connection. + if (_.get(file, 'connection') === _.get(scope, 'connection')) { + // Save the model name thanks to the given table name. + const modelName = _.get(file, 'tableName'); + scope.models[modelName] = file; - // Save the model name thanks to the given table name. - modelName = JSON.parse(fs.readFileSync(path.resolve(file))).tableName; - scope.models[modelName] = JSON.parse(fs.readFileSync(path.resolve(file))); - - if (!_.isEmpty(history) && history.hasOwnProperty(_.capitalize(modelName))) { - _.set(scope.models, modelName + '.oldAttributes', _.get(history, _.capitalize(modelName) + '.attributes')); - } else { - _.set(scope.models, modelName + '.oldAttributes', {}); - } - - // First, we need to know if the table already exists. - scope.db.schema.hasTable(modelName).then(function (exists) { - - // If the table doesn't exist. - if (!exists) { - - // Builder: add needed options specified in the model - // for each option. - _.forEach(scope.models[modelName].options, function (value, option) { - builder.options(scope.models, modelName, value, option); - }); - - // Builder: create template for each attribute-- either with a column type - // or with a relationship. - _.forEach(scope.models[modelName].attributes, function (details, attribute) { - if (details.type && _.isString(details.type)) { - builder.types(scope.models, modelName, details, attribute); - } else if (_.isString(details.collection) || _.isString(details.model)) { - builder.relations(scope.models, modelName, details, attribute); - } - }); - - // Builder: create and drop the table. - builder.createTable(scope.models, modelName); + if (!_.isEmpty(history) && history.hasOwnProperty(_.capitalize(modelName))) { + _.set(scope.models, modelName + '.oldAttributes', _.get(history, _.capitalize(modelName) + '.attributes')); + } else { + _.set(scope.models, modelName + '.oldAttributes', {}); } - // If the table already exists. - else { + // First, we need to know if the table already exists. + scope.db.schema.hasTable(modelName).then(function (exists) { + // If the table doesn't exist. + if (!exists) { + // Builder: add needed options specified in the model + // for each option. + _.forEach(scope.models[modelName].options, function (value, option) { + builder.options(scope.models, modelName, value, option); + }); - // Ideally, we need to verify the table properties here - // to see if they still are the same. - - // Parse every attribute. - _.forEach(scope.models[modelName].attributes, function (details, attribute) { - // TODO: - // - Column is existing ? - // -- YES: - // --- Compare current type with last one (nullable, maxLenght, type, defaultValue). - // ---- Updated ? Drop column, and create a new one. - // ---- Not updated ? Do nothing. - // -- NO: - // --- Add the new column - - // scope.db.schema.hasColumn(modelName, attribute).then(function (exists) { - // - // }).catch(function (err) { - // console.log(err); - // }); - - // Verify if a column already exists for the attribute. - scope.models[modelName].newAttributes = {}; - - // If it's a new attribute. - if (!scope.models[modelName].oldAttributes.hasOwnProperty(attribute)) { - // Save the attribute as a new attribute. - scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details); - - // Builder: create template for each attribute-- either with a column type - // or with a relationship. + // Builder: create template for each attribute-- either with a column type + // or with a relationship. + _.forEach(scope.models[modelName].attributes, function (details, attribute) { if (details.type && _.isString(details.type)) { - builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute); + builder.types(scope.models, modelName, details, attribute); } else if (_.isString(details.collection) || _.isString(details.model)) { - builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute); + builder.relations(scope.models, modelName, details, attribute); } + }); - // Builder: select the table. - builder.selectTable(scope.models, modelName); - } else { - // If the column already exists. + // Builder: create and drop the table. + builder.createTable(scope.models, modelName); + } else { + // If the table already exists. - let toDrop = false; + // Set new attributes object + _.set(scope.models[modelName], 'newAttributes', {}); - // Try to identify relation attribute update - if (details.hasOwnProperty('collection') && details.hasOwnProperty('via') && - (_.get(scope.models[modelName].oldAttributes[attribute], 'collection') !== details.collection || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) { - toDrop = true; - } else if (details.hasOwnProperty('model') && details.hasOwnProperty('via') && - (_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) { - toDrop = true; - } else if (details.hasOwnProperty('model') && - (_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model)) { - toDrop = true; - } else if (!_.isUndefined(details.type) && _.get(scope.models[modelName].oldAttributes[attribute], 'type') !== _.get(details, 'type')) { - toDrop = true; - } else if (!_.isUndefined(details.defaultValue) && _.get(scope.models[modelName].oldAttributes[attribute], 'defaultValue') === _.get(details, 'defaultValue')) { - toDrop = true; - } else if (!_.isUndefined(details.maxLength) && _.get(scope.models[modelName].oldAttributes[attribute], 'maxLength') === _.get(details, 'maxLength')) { - toDrop = true; - } else if (!_.isUndefined(details.nullable) && _.get(scope.models[modelName].oldAttributes[attribute], 'nullable') === _.get(details, 'nullable')) { - toDrop = true; - } - - // The attribute has been updated. - // We will drop it then create it again with the new options. - if (toDrop) { + // Parse every attribute. + _.forEach(scope.models[modelName].attributes, function (details, attribute) { + // If it's a new attribute. + if (!scope.models[modelName].oldAttributes.hasOwnProperty(attribute)) { // Save the attribute as a new attribute. scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details); // Builder: create template for each attribute-- either with a column type // or with a relationship. if (details.type && _.isString(details.type)) { - builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, toDrop); + builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute); } else if (_.isString(details.collection) || _.isString(details.model)) { - builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, toDrop); + builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute); } + } else { + // If it's an existing attribute. - // Builder: select the table. - builder.selectTable(scope.models, modelName); + // Try to identify attribute updates + const toDrop = () => { + if (details.hasOwnProperty('collection') && details.hasOwnProperty('via') && + (_.get(scope.models[modelName].oldAttributes[attribute], 'collection') !== details.collection || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) { + return true; + } else if (details.hasOwnProperty('model') && details.hasOwnProperty('via') && + (_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model || _.get(scope.models[modelName].oldAttributes[attribute], 'via') !== details.via)) { + return true; + } else if (details.hasOwnProperty('model') && + (_.get(scope.models[modelName].oldAttributes[attribute], 'model') !== details.model)) { + return true; + } else if (!_.isUndefined(details.type) && _.get(scope.models[modelName].oldAttributes[attribute], 'type') !== _.get(details, 'type')) { + return true; + } else if (!_.isUndefined(details.defaultValue) && _.get(scope.models[modelName].oldAttributes[attribute], 'defaultValue') === _.get(details, 'defaultValue')) { + return true; + } else if (!_.isUndefined(details.maxLength) && _.get(scope.models[modelName].oldAttributes[attribute], 'maxLength') === _.get(details, 'maxLength')) { + return true; + } else if (!_.isUndefined(details.nullable) && _.get(scope.models[modelName].oldAttributes[attribute], 'nullable') === _.get(details, 'nullable')) { + return true; + } else { + return false; + } + }(); + + // The attribute has been updated. + // We will drop it then create it again with the new options. + if (toDrop) { + // Save the attribute as a new attribute. + scope.models[modelName].newAttributes[attribute] = _.cloneDeep(details); + + // Builder: create template for each attribute-- either with a column type + // or with a relationship. + if (details.type && _.isString(details.type)) { + builder.types(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, toDrop); + } else if (_.isString(details.collection) || _.isString(details.model)) { + builder.relations(scope.models, modelName, scope.models[modelName].newAttributes[attribute], attribute, toDrop); + } + } } - } - }); - } - }); + }); - return new Promise((resolve) => { - asyncFunction(file, resolve); - }); + // For lightweight migration file, + // only call this when new attributes are detected. + if (!_.isEmpty(scope.models[modelName].newAttributes)) { + // Builder: select the table. + builder.selectTable(scope.models, modelName); + } + } + }); + + return new Promise((resolve) => { + asyncFunction(filepath, resolve); + }); + } + } catch (e) { + return cb.invalid(e); } }); diff --git a/packages/strapi-generate-migrations/lib/builder/selectTable.js b/packages/strapi-generate-migrations/lib/builder/selectTable.js index 5ab2ec4bbb..d3ff2261b2 100644 --- a/packages/strapi-generate-migrations/lib/builder/selectTable.js +++ b/packages/strapi-generate-migrations/lib/builder/selectTable.js @@ -17,38 +17,48 @@ const _ = require('lodash'); module.exports = function (models, modelName) { - models[modelName].up = {}; + if (!models[modelName].hasOwnProperty('up')) { + models[modelName].up = { + drop: '', + others: '' + }; + } // Template: select the table for the `up` export. // Every attribute with `create` key will be added in this template. const tplSelectTableUp = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'up.template'), 'utf8'); - models[modelName].up.drop = _.unescape(_.template(tplSelectTableUp)({ + models[modelName].up.drop += _.unescape(_.template(tplSelectTableUp)({ models: models, tableName: modelName, attributes: models[modelName].newAttributes, toDrop: true })); - models[modelName].up.others = _.unescape(_.template(tplSelectTableUp)({ + models[modelName].up.others += _.unescape(_.template(tplSelectTableUp)({ models: models, tableName: modelName, attributes: models[modelName].newAttributes, toDrop: false })); - models[modelName].down = {}; + if (!models[modelName].hasOwnProperty('down')) { + models[modelName].down = { + drop: '', + others: '' + }; + } // Template: select the table for the `down` export. // Every attribute with `delete` key will be added in this template. const tplSelectTableDown = fs.readFileSync(path.resolve(__dirname, '..', '..', 'templates', 'builder', 'tables', 'select', 'down.template'), 'utf8'); - models[modelName].down.drop = _.unescape(_.template(tplSelectTableDown)({ + models[modelName].down.drop += _.unescape(_.template(tplSelectTableDown)({ models: models, tableName: modelName, attributes: models[modelName].newAttributes, toDrop: true })); - models[modelName].down.others = _.unescape(_.template(tplSelectTableDown)({ + models[modelName].down.others += _.unescape(_.template(tplSelectTableDown)({ models: models, tableName: modelName, attributes: models[modelName].newAttributes,