swagelize
Version:
Generate Sequelize model definitions and DAO from a Swagger 2.0 schema
971 lines (850 loc) • 65.5 kB
JavaScript
const fs = require('fs');
var util = require('util');
/**
* @namespace
* @property {object} Sequelize
* @property {function} Sequelize.BLOB
* @property {function} Sequelize.ENUM
* @property {function} Sequelize.STRING
* @property {function} Sequelize.STRING.BINARY
* @property {function} Sequelize.DATE
* @property {function} Sequelize.ARRAY
* @property {function} Sequelize.BOOLEAN
* @property {function} Sequelize.DOUBLE
* @property {function} Sequelize.FLOAT
* @property {function} Sequelize.INTEGER
* @property {function} Sequelize.BIGINT
*/
var Sequelize = require('sequelize');
var dialect = 'mysql';
/**
* @param {string} newDialect
* @returns {*}
*/
function setDialect(newDialect) {
if (['mysql', 'mariadb', 'sqlite', 'postgres', 'mssql'].indexOf(newDialect) === -1) {
throw new Error('Unknown sequalize dialect');
}
dialect = newDialect;
}
/**
*
* @param {Object|string} swaggerPropertySchema
* @param {Object} swaggerPropertySchema.properties
* @param {Object} swaggerPropertySchema.$ref
* @param {Array} swaggerPropertySchema.enum
* @param {string} swaggerPropertySchema.type
* @param {string} swaggerPropertySchema.format
* @param {Object|string} swaggerPropertySchema.items
* @returns {*}
*/
function getSequalizeType(swaggerPropertySchema) {
if (typeof swaggerPropertySchema === 'string') {
swaggerPropertySchema = {
type: swaggerPropertySchema
}
}
if (swaggerPropertySchema.properties) {
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema.properties));
console.log('Cannot handle complex subschemas (yet?), falling back to blob');
return Sequelize.BLOB;
}
if (swaggerPropertySchema.$ref) {
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema.$ref));
console.log('Cannot handle $ref (yet?), falling back to blob');
return Sequelize.BLOB;
}
if (swaggerPropertySchema.enum) {
return Sequelize.ENUM.apply(null, swaggerPropertySchema.enum);
}
// as seen http://swagger.io/specification/#dataTypeType
switch (swaggerPropertySchema.type) {
case 'string':
switch (swaggerPropertySchema.format || "") {
case 'byte':
case 'binary':
if (swaggerPropertySchema.maxLength > 5592415) {
return Sequelize.BLOB('long');
}
if (swaggerPropertySchema.maxLength > 21845) {
return Sequelize.BLOB('medium');
}
// NOTE: VARCHAR(255) may container 255 multibyte chars: it's _NOT_ byte delimited
if (swaggerPropertySchema.maxLength > 255) {
return Sequelize.BLOB();
}
return Sequelize.STRING.BINARY;
case 'date':
return Sequelize.DATEONLY;
case 'date-time':
//return Sequelize.DATETIME; //not working?
return Sequelize.DATE;
default:
if (swaggerPropertySchema.maxLength) {
// http://stackoverflow.com/questions/13932750/tinytext-text-mediumtext-and-longtext-maximum-sto
// http://stackoverflow.com/questions/7755629/varchar255-vs-tinytext-tinyblob-and-varchar65535-v
// NOTE: text may be in multibyte format!
if (swaggerPropertySchema.maxLength > 5592415) {
return Sequelize.TEXT('long');
}
if (swaggerPropertySchema.maxLength > 21845) {
return Sequelize.TEXT('medium');
}
// NOTE: VARCHAR(255) may container 255 multibyte chars: it's _NOT_ byte delimited
if (swaggerPropertySchema.maxLength > 255) {
return Sequelize.TEXT();
}
}
return Sequelize.STRING; // === VARCHAR
}
case 'array':
if (dialect === 'postgres') {
return Sequelize.ARRAY(getSequalizeType(swaggerPropertySchema.items));
}
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema));
console.log('Can only handle array for postgres (yet?), see http://docs.sequelizejs.com/en/latest/api/datatypes/#array, falling back to blob');
return Sequelize.BLOB;
case 'boolean':
return Sequelize.BOOLEAN;
case 'integer':
switch (swaggerPropertySchema.format || "") {
case 'int32':
if (typeof swaggerPropertySchema.minimum === "number" && swaggerPropertySchema.minimum >= 0) {
return Sequelize.INTEGER.UNSIGNED;
}
return Sequelize.INTEGER;
default:
if (typeof swaggerPropertySchema.minimum === "number" && swaggerPropertySchema.minimum >= 0) {
return Sequelize.BIGINT.UNSIGNED;
}
return Sequelize.BIGINT;
}
case 'number':
switch (swaggerPropertySchema.format || "") {
case 'float':
return Sequelize.FLOAT;
default:
return Sequelize.DOUBLE;
}
default:
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema));
console.log('Unknown data type, falling back to blob');
return Sequelize.BLOB;
}
}
/**
* This function returns the Sequelize type of the swagger property schema
* Because of natural behaviour of util.inspect function used to stringify the property we need to use tokens
* that will be removed afterward
* @param swaggerPropertySchema
* @returns {*}
*/
function getSequalizeTypeString(swaggerPropertySchema, logThis) {
if (typeof swaggerPropertySchema === 'string') {
swaggerPropertySchema = {
type: swaggerPropertySchema
}
}
if (swaggerPropertySchema.properties) {
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema.properties));
console.log('Cannot handle complex subschemas (yet?), falling back to blob');
return '##DataTypes.BLOB##';
}
if (swaggerPropertySchema.$ref) {
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema.$ref));
console.log('$ref means its a foreign key, it will be handle separatly');
console.log('Falling back to blob for now, whole property will be replaced in a method later');
return '##DataTypes.BLOB##';
}
if (swaggerPropertySchema.enum) {
let result = "";
for (let i = 0; i < swaggerPropertySchema.enum.length; i++){
result += "'" + swaggerPropertySchema.enum[i] + "', ";
}
result = result.substring(0, result.length - 2);
return '##DataTypes.ENUM(' + result + ')##';
}
// as seen http://swagger.io/specification/#dataTypeType
if (logThis){
console.log('swaggerPropertySchema => ', swaggerPropertySchema);
}
switch (swaggerPropertySchema.type) {
case 'string':
switch (swaggerPropertySchema.format || "") {
case 'byte':
case 'binary':
if (swaggerPropertySchema.maxLength > 5592415) {
return '##DataTypes.BLOB(\'long\')##';
}
if (swaggerPropertySchema.maxLength > 21845) {
return '##DataTypes.BLOB(\'medium\')##';
}
// NOTE: VARCHAR(255) may container 255 multibyte chars: it's _NOT_ byte delimited
if (swaggerPropertySchema.maxLength > 255) {
return '##DataTypes.BLOB##';
}
return '##DataTypes.STRING.BINARY##';
case 'date':
return '##DataTypes.DATEONLY##';
case 'date-time':
//return Sequelize.DATETIME; //not working?
return '##DataTypes.DATE##';
default:
if (swaggerPropertySchema.maxLength) {
// http://stackoverflow.com/questions/13932750/tinytext-text-mediumtext-and-longtext-maximum-sto
// http://stackoverflow.com/questions/7755629/varchar255-vs-tinytext-tinyblob-and-varchar65535-v
// NOTE: text may be in multibyte format!
if (swaggerPropertySchema.maxLength > 5592415) {
return '##DataTypes.TEXT(\'long\')##';
}
if (swaggerPropertySchema.maxLength > 21845) {
return '##DataTypes.TEXT(\'medium\')##';
}
// NOTE: VARCHAR(255) may container 255 multibyte chars: it's _NOT_ byte delimited
if (swaggerPropertySchema.maxLength > 255) {
return '##DataTypes.TEXT##';
}
}
return '##DataTypes.STRING##'; // === VARCHAR
}
case 'array':
if (dialect === 'postgres') {
return Sequelize.ARRAY(getSequalizeType(swaggerPropertySchema.items));
}
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema));
console.log('Arrays functionaly means its a 1-N relations, it will be handle separatly');
console.log('Falling back to blob for now, whole property will be replaced in a method later');
return '##DataTypes.ARRAY##';
case 'boolean':
return '##DataTypes.BOOLEAN##';
case 'integer':
switch (swaggerPropertySchema.format || "") {
case 'int32':
if (typeof swaggerPropertySchema.minimum === "number" && swaggerPropertySchema.minimum >= 0) {
return '##DataTypes.INTEGER.UNSIGNED##';
}
return '##DataTypes.INTEGER##';
default:
if (typeof swaggerPropertySchema.minimum === "number" && swaggerPropertySchema.minimum >= 0) {
return '##DataTypes.BIGINT.UNSIGNED##';
}
return '##DataTypes.BIGINT##';
}
case 'number':
switch (swaggerPropertySchema.format || "") {
case 'float':
return '##DataTypes.FLOAT##';
default:
return '##DataTypes.DOUBLE##';
}
default:
console.log('Warning: encountered', JSON.stringify(swaggerPropertySchema));
console.log('Unknown data type, falling back to blob');
return '##DataTypes.BLOB##';
}
}
/**
* Will generate folder structure needed for the generation
*/
function generateFolders() {
return new Promise(function(resolve, reject){
try {
console.log('Creating models folder');
fs.mkdirSync('./models');
} catch (err) {
console.log('models folder already exists');
}
try {
console.log('Creating dao folder');
fs.mkdirSync('./dao');
} catch (err) {
console.log('dao folder already exists');
}
resolve();
});
}
/**
* GenerateFile ASYNC
* @param folderPath
* @param fileName
* @param stringContent
* @returns {Promise}
*/
function generateFile(folderPath, fileName, stringContent) {
return new Promise(function(resolve, reject){
fs.writeFile(folderPath + '/' + fileName, stringContent, function(err) {
if(err) {
return console.log(err);
}
resolve();
});
});
}
/**
* Syncrhone version of generate file
* @param folderPath
* @param fileName
* @param stringContent
*/
function generateFileSync(folderPath, fileName, stringContent) {
fs.writeFileSync(folderPath + '/' + fileName, stringContent);
}
/**
* Delete specified file
* @param folderPath
* @param fileName
* @returns {Promise}
*/
function deleteFile(folderPath, fileName) {
return new Promise(function(resolve, reject){
fs.unlink(folderPath + '/' + fileName,function(err){
if(err) reject(err);
resolve();
});
});
}
/**
* Generate 1 model, from a swagger.json definition
* const readFile = fs.readFileSync('./swagger/swagger.json', 'utf-8');
* const swaggerSpec = JSON.parse(readFile);
* const generated = swaggerSequelize.generate(swaggerSpec.definitions[propertyName]);
* @param schema
*/
function generate (schema) {
var result = JSON.parse(JSON.stringify(schema.properties));
Object.keys(result).forEach((propertyName) => {
var propertySchema = result[propertyName];
// BEGIN: Promote Attribute to primaryKey with autoIncrement
if(propertySchema['x-primary-key'] === true) {
propertySchema.primaryKey = true;
propertySchema.autoIncrement = true;
propertySchema.allowNull = false;
}
// END: Promote Attribute to primaryKey with autoIncrement
propertySchema.type = getSequalizeType(propertySchema);
if (propertySchema.default) {
propertySchema.defaultValue = propertySchema.default;
}
});
return result;
}
function findPrimaryKey(theModel, theModelSchema){
const parsed = JSON.parse(JSON.stringify(theModelSchema));
if (!parsed['x-primary-key']){
throw new Error('No primary key referenced for model ' + theModel + ' !!!');
}
return {pks: parsed['x-primary-key']};
}
/**
* Because of natural function in utils.inspect, We are forced to use some tokens to display what we want.
* This methods removes the tokens
* @param generated
* @returns {string}
*/
function removeEscaped(generated){
return util.inspect(generated,false, 5, false)
.replace(/'##/g, '')
.replace(/##'/g, '')
.replace(/\\'/g, "'")
.replace(/\\\\'/g, "\\'");
}
/**
* generate the index.js for sequelize if and only if it doesn't exists
*/
function generateModelIndex() {
let sequelizeModelContent = '\'use strict\';\n';
sequelizeModelContent += '\n';
sequelizeModelContent += 'const fs = require(\'fs\');\n';
sequelizeModelContent += 'const path = require(\'path\');\n';
sequelizeModelContent += 'const Sequelize = require(\'sequelize\');\n';
sequelizeModelContent += 'const basename = path.basename(__filename);\n';
sequelizeModelContent += 'const env = process.env.NODE_ENV || \'local\';\n';
sequelizeModelContent += 'const config = require(path.join(__dirname + \'/../config/config.js\'))[env];\n';
sequelizeModelContent += '\n';
sequelizeModelContent += 'const db = {};\n';
sequelizeModelContent += 'let sequelize = null;\n';
sequelizeModelContent += 'if (config.use_env_constiable) {\n';
sequelizeModelContent += '\tsequelize = new Sequelize(process.env[config.use_env_constiable], config);\n';
sequelizeModelContent += '} else {\n';
sequelizeModelContent += '\tsequelize = new Sequelize(config.database, config.username, config.password, {\n';
sequelizeModelContent += '\t\thost: config.host,\n';
sequelizeModelContent += '\t\tport: config.dbPort,\n';
sequelizeModelContent += '\t\tlogging: config.logging,\n';
sequelizeModelContent += '\t\tdialect: config.dialect,\n';
sequelizeModelContent += '\t\tpool: {\n';
sequelizeModelContent += '\t\t\tmaxConnections: config.maxConnections,\n';
sequelizeModelContent += '\t\t\tmaxIdleTime: config.maxIdleTime\n';
sequelizeModelContent += '\t\t}\n';
sequelizeModelContent += '\t});\n';
sequelizeModelContent += '\n';
sequelizeModelContent += '\t/* UNCOMENT BELOW TO TEST DATABASE CONNEXION*/\n';
sequelizeModelContent += '\t// sequelize.authenticate().then(function () {\n';
sequelizeModelContent += '\t// console.log("Connexion HAS WORKED! ");\n';
sequelizeModelContent += '\t// }).catch(function (err) {\n';
sequelizeModelContent += '\t// console.log("Connexion FAILED ==> err : ", err);\n';
sequelizeModelContent += '\t// }).done();\n';
sequelizeModelContent += '\n';
sequelizeModelContent += '}\n';
sequelizeModelContent += '\n';
sequelizeModelContent += 'fs\n';
sequelizeModelContent += '\t.readdirSync(__dirname)\n';
sequelizeModelContent += '\t.filter(file => {\n';
sequelizeModelContent += '\t\treturn (file.indexOf(\'.\') !== 0) && (file !== basename) && (file.slice(-3) === \'.js\');\n';
sequelizeModelContent += '\t})\n';
sequelizeModelContent += '\t.forEach(file => {\n';
sequelizeModelContent += '\t\tconst model = sequelize[\'import\'](path.join(__dirname, file));\n';
sequelizeModelContent += '\t\tdb[model.name] = model;\n';
sequelizeModelContent += '\t});\n';
sequelizeModelContent += '\n';
sequelizeModelContent += 'Object.keys(db).forEach(modelName => {\n';
sequelizeModelContent += '\tif (db[modelName].associate) {\n';
sequelizeModelContent += '\t\tdb[modelName].associate(db);\n';
sequelizeModelContent += '\t}\n';
sequelizeModelContent += '});\n';
sequelizeModelContent += '\n';
sequelizeModelContent += 'db.sequelize = sequelize;\n';
sequelizeModelContent += 'db.Sequelize = Sequelize;\n';
sequelizeModelContent += '\n';
sequelizeModelContent += 'module.exports = db;\n';
try {
fs.writeFileSync('./models/index.js', sequelizeModelContent, { flag: 'wx' }, 'utf-8');
} catch (err) {
console.log('models/index.js already exists, NOT OVERRIDING');
}
}
/**
* Generate 1 model, from a PARSED swagger.json definition => supply JSON.parse(JSON.stringify(definition.properties))
*
* Returning a string containing Sequelize type
*
* associations will be populated by found associaitions
*
* example
* const readFile = fs.readFileSync('./swagger/swagger.json', 'utf-8');
* const swaggerSpec = JSON.parse(readFile);
* const generated = swaggerSequelize.generate(swaggerSpec.definitions[propertyName]);
* @param schema
*/
function generateOne (currentModel, currentModelSchema, allModelsSchema, associations) {
var result = currentModelSchema;
// console.log('result => ', result);
const foreignKeys = [];
const throughTable = allModelsSchema[currentModel].throughTable;
Object.keys(result).forEach((propertyName) => { // for each property in model shcema
var propertySchema = result[propertyName];
if (propertySchema.nullable){
propertySchema.allowNull = propertySchema.nullable;
delete propertySchema.nullable;
} else {
propertySchema.allowNull = true;
delete propertySchema.nullable;
}
// If current propertyName is contained withing PKS, promote it to primaryKey (supporting multiple PKS)
const pkeys = allModelsSchema[currentModel]['x-primary-key'];
if (pkeys){
for (let i = 0; i < pkeys.length; i++){
if (propertyName === pkeys[i]) {
propertySchema.primaryKey = true;
propertySchema.allowNull = false;
if (propertySchema['type'] !== 'string' && pkeys.length == 1){
propertySchema.autoIncrement = true;
}
}
}
}
const seqType = getSequalizeTypeString(propertySchema);
// console.log('================> seqType = ', seqType);
propertySchema.type = seqType;
// console.log('================> propertySchema.type = ', propertySchema.type);
// console.log('================> propertySchema = ', propertySchema);
if (propertySchema.enum){
delete propertySchema.enum;
}
if (propertySchema.xml){
delete propertySchema.xml;
}
if (propertySchema.$ref){
const temp = propertySchema.$ref.split('/');
const type = temp[temp.length -1];
const throughTable = propertySchema.throughTable;
const nullable = propertySchema.nullable;
const sourceCardinality = propertySchema.sourceCardinality;
const useThroughTableFields = propertySchema.useThroughTableFields;
foreignKeys.push({
propertyToRemove: propertyName,
propertyType: type,
throughTable: throughTable,
useThroughTableFields:useThroughTableFields,
sourceCardinality: sourceCardinality,
nullable: nullable
});
console.dir(foreignKeys);
}
if (propertySchema.default) {
propertySchema.defaultValue = propertySchema.default;
}
});
// MANAGING FOREIGN KEYS FOR CURRENT MODEL
if (foreignKeys.length > 0){
for(let i = 0; i < foreignKeys.length; i++){
const sourceAttributeName = foreignKeys[i].propertyToRemove;
const typeToFind = foreignKeys[i].propertyType;
delete result[sourceAttributeName];
if (!throughTable){
const pkForTypeToFind = findPrimaryKey(typeToFind, allModelsSchema[typeToFind]);
//flag this association to process l8er
associations[currentModel] = associations[currentModel] || [];
associations[currentModel].push({
referencedModel: typeToFind,
fksUsed: pkForTypeToFind.pks,
throughTable: foreignKeys[i].throughTable,
useThroughTableFields: foreignKeys[i].useThroughTableFields,
sourceCardinality: foreignKeys[i].sourceCardinality,
targetCardinality: foreignKeys[i].throughTable? 'N' : '1',
sourceAttributeName: sourceAttributeName,
nullable: foreignKeys[i].nullable
});
}
}
}
return result;
}
function uncapitalize(text){
return text.charAt(0).toLowerCase() + text.substr(1);
}
function capitalize(text){
return text.charAt(0).toUpperCase() + text.substr(1);
}
/**
* Generate all sequelize models based on completeSwaggerSchema.definitions input
* @param modelSchema
*/
function generateModels(modelSchema) {
return new Promise(function(resolve, reject){
// MANAGING ARRAY TYPES
console.log('============= MANAGING ARRAYS IN DEFINITIONS =============');
let modelSchemas = [];
let arrays = []
let associations = {};
/** index = loop index / key = modelName / value = schema content of the modelName */
for (const [index, [key, value]] of Object.entries(Object.entries(modelSchema))) { // for each entry in swagger spec definitions node
var result = JSON.parse(JSON.stringify(value.properties));
// console.log('PROCESSING ' + key + ' - result : ', result);
Object.keys(result).forEach((propertyName) => { // for each entry in the Object
var propertySchema = result[propertyName];
if (propertySchema.type === 'array'){
let impactedType ='';
let throughTable ='';
// Add property FK to this current model into the referenced item type
if (!propertySchema.items){
throw new Error('swagger definitions contains an array without items definitions : ' + key);
}
if (propertySchema.items.$ref){
const temp = propertySchema.items.$ref.split('/');
impactedType = temp[temp.length-1];
throughTable = propertySchema.throughTable;
useThroughTableFields = propertySchema.useThroughTableFields;
console.log('Referenced ARRAY => ', impactedType + ' for ' + key + ' throughTable = ' + throughTable);
} else if (propertySchema.items.type === 'string') {
console.log('Encountered an ARRAY of string, not processing')
} else {
console.log('Encountered an ARRAY with a type of = ' + propertySchema.items.type + ' for ' + key);
console.log('Not processing it...');
}
// Mark the property to be removed from model, and Model to be impacted
arrays.push({
propertyToRemove: propertyName,
impactedType: impactedType,
toBeReferenced: key,
throughTable: throughTable
});
}
});
if (arrays.length > 0){
for(let i = 0; i < arrays.length; i++){
const refRemove = arrays[i].propertyToRemove;
// console.log('deleting Array propretie : ' + refRemove);
delete result[refRemove];
}
}
const ms = {key: key, modSchema: result};
modelSchemas.push(ms);
}
// IMPACTINC CHANGES DUE TO ARRAYS FOUND
if (arrays.length > 0) {
for (let i = 0; i < modelSchemas.length; i++) { // for each model
const key = modelSchemas[i].key;
const value = modelSchemas[i].modSchema;
for (let i = 0; i < arrays.length; i++) { // for each referenced impacted type in the array
const impactedType = arrays[i].impactedType;
const toBeReferenced = arrays[i].toBeReferenced;
const throughTable = arrays[i].throughTable;
const sourceAttributeName = arrays[i].propertyToRemove;
if (key === impactedType) { // if current model == impactedType in the array
// Found model to be impacted by the array
if (impactedType !== '') {
const tbrSchema = modelSchema[toBeReferenced];
const pkForTypeToFind = findPrimaryKey(toBeReferenced, tbrSchema);
associations[key] = associations[key] || [];
associations[key].push({
referencedModel: toBeReferenced,
sourceCardinality: 'N', // This says toBeReferenced has N key (for arrays N is always the case)
targetCardinality: throughTable? 'N' : '1', // This says if throughTable specified, its [Key]N---N[ToBeReferenced], else [Key]N---1[ToBeReferenced]
sourceAttributeName: sourceAttributeName,
fkUsed: pkForTypeToFind.pks,
throughTable: throughTable,
nullable: true
});
}
}
}
}
}
console.log('============= DONE MANAGING ARRAYS IN DEFINITIONS =============');
// GENERATING MODELS
// INITIALIZATION OF SEQUELIZE FILE WITH DEFINITION
var modelContents = {};
for(let i = 0; i < modelSchemas.length; i++){
const key = modelSchemas[i].key;
const value = modelSchemas[i].modSchema;
// for (const [index, [key, value]] of Object.entries(Object.entries(modelSchema))) {
let sequelizeModelContent = 'module.exports = function(sequelize, DataTypes) {\n';
sequelizeModelContent += '\tvar ' + key +' = sequelize.define(\'' + key + '\', ';
const generated = generateOne(key, value, modelSchema, associations);
console.log('generated =>', generated);
sequelizeModelContent += removeEscaped(generated);
console.log('sequelizeModelContent =>', sequelizeModelContent);
sequelizeModelContent += ', {';
sequelizeModelContent += '\n\t\ttableName: \'' + key + '\',';
sequelizeModelContent += '\n\t\ttimestamps: false';
sequelizeModelContent += '\n\t});';
sequelizeModelContent += '\n';
modelContents[key] = modelContents[key] || {};
modelContents[key] = {model: key, modelContent: sequelizeModelContent};
}
// Manage associations
console.log('ASSOCIATIONS DETAILS =====>');
console.dir(associations, {depth:null, colors:true});
console.log('MANANING FIRST LINE OF ASSOCIATION IN EVERY MODELS : Model.associate = function (models)');
var modelFirstLineDone = {};
// for (const [index, [associationKey, associationValue]] of Object.entries(Object.entries(associations))) {
// modelContents[associationKey].modelContent += '\n\t ' + associationKey + '.associate = function (models) {\n';
// modelFirstLineDone[associationKey] = modelFirstLineDone[associationKey] || {};
// modelFirstLineDone[associationKey] = {model: associationKey, done: true};
// for (let i = 0; i < associationValue.length; i++){
// modelFirstLineDone[associationValue[i].referencedModel] = modelFirstLineDone[associationValue[i].referencedModel] || {};
// if (!(modelFirstLineDone[associationValue[i].referencedModel].done)){
// modelContents[associationValue[i].referencedModel].modelContent += '\n\t ' + associationValue[i].referencedModel + '.associate = function (models) {\n';
// modelFirstLineDone[associationValue[i].referencedModel] = {model: associationValue[i].referencedModel, done: true};
// }
//
// }
// }
for (const [index, [associationKey, associationValue]] of Object.entries(Object.entries(associations))) {
modelFirstLineDone[associationKey] = modelFirstLineDone[associationKey] || {};
if (!(modelFirstLineDone[associationKey].done)) {
modelContents[associationKey].modelContent += '\n\t ' + associationKey + '.associate = function (models) {\n';
modelFirstLineDone[associationKey] = {model: associationKey, done: true};
}
for (let i = 0; i < associationValue.length; i++){
modelFirstLineDone[associationValue[i].referencedModel] = modelFirstLineDone[associationValue[i].referencedModel] || {};
if (!(modelFirstLineDone[associationValue[i].referencedModel].done)){
modelContents[associationValue[i].referencedModel].modelContent += '\n\t ' + associationValue[i].referencedModel + '.associate = function (models) {\n';
modelFirstLineDone[associationValue[i].referencedModel] = {model: associationValue[i].referencedModel, done: true};
}
}
}
for(let i = 0; i < modelSchemas.length; i++) {
const currentKey = modelSchemas[i].key;
if (associations[currentKey]){
console.log('Managing associations for ' + currentKey);
for (const [index, [associationKey, associationValue]] of Object.entries(Object.entries(associations))) {
if (currentKey === associationKey){
for (let i = 0; i< associationValue.length; i++){
// console.log('key = ', associationKey);
// console.log('value = ', associationValue[i]);
// console.log('throughTable = ', associationValue[i].throughTable);
const referencedModel = associationValue[i].referencedModel;
const throughTable = associationValue[i].throughTable;
const sourceCardinality = associationValue[i].sourceCardinality;
const targetCardinality = associationValue[i].targetCardinality;
const sourceAttributeName = associationValue[i].sourceAttributeName;
let throughTableStringValue = '';
if(throughTable){
throughTableStringValue = '\'' + throughTable + '\',';
if (modelContents[throughTable]) {
throughTableStringValue = '{model:models.' + throughTable;
console.log('modelSchema[throughTable] => ', modelSchema[throughTable]);
if (modelSchema[throughTable].uniqueFks){
throughTableStringValue += '},'
} else {
throughTableStringValue += ', unique:false},'
}
}
}
// If cardinality is definined, it can only be 1 or N
if (sourceCardinality && (sourceCardinality !== '1' && sourceCardinality !== 'N')){
throw new Error('Cardinality can only be 1 or N, encountered ' + sourceCardinality + ' for model ' + associationKey);
}
// PART 1 => ASSOCIATE IN MODEL REFERENCING
// if (throughTable){ // N-N associations
console.log('\t[' + associationKey + ']' + sourceCardinality + '---' + targetCardinality + '[' + referencedModel + ']');
console.log('\tManaging into ' + associationKey + ' model');
if (sourceCardinality === 'N' && targetCardinality === 'N') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[associationKey].modelContent += '\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey +
'.belongsToMany(models.'+ referencedModel +', {as: \'' + uncapitalize(referencedModel) + 's' + 'Source\',' +
'through :' + throughTableStringValue +
'foreignKey: \'id_' + uncapitalize(associationKey) + 'Source\',' +
'otherKey: \'id_' + uncapitalize(referencedModel) + 'Target\'});\n';
} else {
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey +
'.belongsToMany(models.'+ referencedModel +', {as: \'' + sourceAttributeName + '\',' +
'through : ' + throughTableStringValue +
'foreignKey: \'id_' + uncapitalize(associationKey) + '\',' +
'otherKey: \'id_' + uncapitalize(referencedModel) + '\'});\n';
// modelContents[associationKey].modelContent += '\t};\n';
}
} else if (sourceCardinality === 'N' && targetCardinality === '1') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey + '.belongsTo(models.'+ referencedModel +', {\n';
modelContents[associationKey].modelContent += '\t\t\tonDelete:\'CASCADE\',\n'; // TODO > define proper onDelete, onUpdate strategy here
modelContents[associationKey].modelContent += '\t\t\tforeignKey: \'id_' + uncapitalize(referencedModel) + '_parent\'\n';
modelContents[associationKey].modelContent += '\t\t});\n';
// modelContents[associationKey].modelContent += '\t};\n';
} else {
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey + '.belongsTo(models.'+ referencedModel +', {\n';
modelContents[associationKey].modelContent += '\t\t\tonDelete:\'CASCADE\',\n'; // TODO > define proper onDelete, onUpdate strategy here
modelContents[associationKey].modelContent += '\t\t\tforeignKey: \'id_' + uncapitalize(referencedModel) + '\'\n';
modelContents[associationKey].modelContent += '\t\t});\n';
// modelContents[associationKey].modelContent += '\t};\n';
}
} else if (sourceCardinality === '1' && targetCardinality === 'N') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey + '.belongsTo(models.'+ referencedModel +', {\n';
modelContents[associationKey].modelContent += '\t\t\tonDelete:\'CASCADE\',\n'; // TODO > define proper onDelete, onUpdate strategy here
modelContents[associationKey].modelContent += '\t\t\tforeignKey: \'id_' + uncapitalize(referencedModel) + '_parent\'\n';
modelContents[associationKey].modelContent += '\t\t});\n';
// modelContents[associationKey].modelContent += '\t};\n';
} else {
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey + '.belongsTo(models.'+ referencedModel +', {\n';
modelContents[associationKey].modelContent += '\t\t\tonDelete:\'CASCADE\',\n'; // TODO > define proper onDelete, onUpdate strategy here
modelContents[associationKey].modelContent += '\t\t\tforeignKey: \'id_' + uncapitalize(referencedModel) + '\'\n';
modelContents[associationKey].modelContent += '\t\t});\n';
// modelContents[associationKey].modelContent += '\t};\n';
}
} else if (sourceCardinality === '1' && targetCardinality === '1') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey + '.belongsTo(models.'+ referencedModel +', {\n';
modelContents[associationKey].modelContent += '\t\t\tonDelete:\'CASCADE\',\n'; // TODO > define proper onDelete, onUpdate strategy here
modelContents[associationKey].modelContent += '\t\t\tforeignKey: \'id_' + uncapitalize(referencedModel) + '_parent\'\n';
modelContents[associationKey].modelContent += '\t\t});\n';
// modelContents[associationKey].modelContent += '\t};\n';
} else {
modelContents[associationKey].modelContent += '\n';
// modelContents[associationKey].modelContent += '\t ' + associationKey + '.associate = function (models) {\n';
modelContents[associationKey].modelContent += '\t\tmodels.' + associationKey + '.belongsTo(models.'+ referencedModel +', {\n';
modelContents[associationKey].modelContent += '\t\t\tonDelete:\'CASCADE\',\n'; // TODO > define proper onDelete, onUpdate strategy here
modelContents[associationKey].modelContent += '\t\t\tforeignKey: \'id_' + sourceAttributeName + '\'\n';
modelContents[associationKey].modelContent += '\t\t});\n';
// modelContents[associationKey].modelContent += '\t};\n';
}
} else {
throw new Error ('There is a problem with cardinalities');
}
// PART 2 => ASSOCIATE IN MODEL REFERENCED
console.log('\tManaging into ' + referencedModel + ' model');
if (sourceCardinality === 'N' && targetCardinality === 'N') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel +
'.belongsToMany(models.'+ associationKey +', {as: \'' + uncapitalize(associationKey) + 's' + 'Target\',' +
'through: ' + throughTableStringValue +
'foreignKey: \'id_' + uncapitalize(referencedModel) + 'Target\',' +
'otherKey: \'id_' + uncapitalize(associationKey) + 'Source\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
} else {
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel +
'.belongsToMany(models.'+ associationKey +', {as: \'' + uncapitalize(associationKey) + 's\',' +
'through: ' + throughTableStringValue +
'foreignKey: \'id_' + uncapitalize(referencedModel) + '\',' +
'otherKey: \'id_' + uncapitalize(associationKey) + '\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
}
} else if (sourceCardinality === 'N' && targetCardinality === '1') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel + '.hasMany(models.'+
associationKey +', {foreignKey: \'id_'+ uncapitalize(referencedModel) + '_parent\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
} else {
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel + '.hasMany(models.'+ associationKey +
', {as: \'' + sourceAttributeName + '\', foreignKey: \'id_'+ uncapitalize(referencedModel) + '\', onDelete:\'CASCADE\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
}
} else if (sourceCardinality === '1' && targetCardinality === 'N') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel + '.hasOne(models.'+ associationKey +
', {foreignKey: \'id_'+ uncapitalize(referencedModel) + '_parent\'});\n';
modelContents[referencedModel].modelContent += '\t};\n';
} else {
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel + '.hasOne(models.'+ associationKey +
', {foreignKey: \'id_'+ uncapitalize(referencedModel) + '\', onDelete:\'CASCADE\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
}
} else if (sourceCardinality === '1' && targetCardinality === '1') {
if (associationKey === referencedModel) { // Recursive relation
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel + '.hasOne(models.'+ associationKey +
', {foreignKey: \'id_'+ uncapitalize(referencedModel) + '_parent\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
} else {
modelContents[referencedModel].modelContent += '\n';
// modelContents[referencedModel].modelContent += '\t ' + referencedModel + '.associate = function (models) {\n';
modelContents[referencedModel].modelContent += '\t\tmodels.' + referencedModel + '.hasOne(models.'+ associationKey +
', {foreignKey: \'id_'+ sourceAttributeName + '\', onDelete:\'CASCADE\'});\n';
// modelContents[referencedModel].modelContent += '\t};\n';
}
} else {
throw new Error('this error won\'t happen ever, because error already thrown sooner');
}
}
}
}
// modelContents[currentKey].modelContent += '\t};\n';
}
}
console.log('MANANING LAST LINE OF ASSOCIATION IN EVERY MODELS : };');
console.log('ASSOCIATIONS DETAILS =====>');
console.dir(associations, {depth:null, colors:true});
var modelLastLineDone = {};
// for (const [index, [associationKey, associationValue]] of Object.entries(Object.entries(associations))) {
// modelContents[associationKey].modelContent += '\t};\n';
// modelLastLineDone[associationKey] = modelLastLineDone[associationKey] || {};
// modelLastLineDone[associationKey] = {model: associationKey, done: true};
// for (let i = 0; i < associationValue.length; i++){
// console.log('DEBUG REMOVE THIS associationValue[i].referencedModel = ', associationValue[i].referencedModel);