· 9 years ago · Oct 19, 2016, 08:30 PM
1'use strict';
2
3const argv = require('minimist')(process.argv.slice(2));
4const jsonfile = require('jsonfile');
5const winston = require('winston');
6const path = require('path');
7const fs = require('fs');
8const async = require('async');
9const Promise = require('promise');
10const sanitize = require("sanitize-filename");
11const migrationFolder = __dirname + '/database/migrations';
12
13let config = {};
14winston.cli();
15winston.level = 'silly';
16
17function main(argv) {
18 process.env.NODE_ENV = argv['env'] || 'development';
19 config = require(__dirname + '/config/config');
20
21 if (!fs.existsSync(migrationFolder)) {
22 fs.mkdirSync(migrationFolder);
23 }
24
25 const command = Object.keys(argv)[1],
26 env = process.env.NODE_ENV;
27
28 switch (command) {
29
30 case 'create':
31 let name = argv[command];
32 if (name !== true) {
33 module.exports.create(`${(new Date()).getTime()}-${argv[command].trim()}`);
34 } else {
35 console.log('Migration name required.')
36 }
37 break;
38
39 case 'run':
40 module.exports.run(env);
41 break;
42
43 case 'undo':
44 module.exports.undo(env);
45 break;
46
47 case 'help':
48 module.exports.help();
49 break;
50
51 default:
52 module.exports.help();
53 break;
54 }
55}
56
57module.exports = {
58
59 create(name) {
60 const file = `${migrationFolder}/${sanitize(name)}.json`;
61
62 jsonfile.writeFile(file, {
63 dbAlias: 'MASTER_DB',
64 run: 'Your forward MYSQL query',
65 undo: 'Your undo query'
66 }, {spaces: 2}, err => {
67 if (err) {
68 console.log(err);
69 } else {
70 console.log(`Migration file created: ${file}`);
71 }
72 });
73 },
74
75 run(env) {
76 console.log(`Running: Using ${env} environment`);
77
78 const mysql = require('anytv-node-mysql'),
79 files = module.exports.importFiles(),
80 connectedDBs = [],
81 tasks = [],
82 migrationNames = Object.keys(files).sort((a, b) => {
83 return parseInt(a.split('-')[0]) - parseInt(b.split('-')[0]);
84 });
85
86 migrationNames.forEach(migrationName => {
87 const file = files[migrationName],
88 alias = file.dbAlias;
89
90 if (connectedDBs.indexOf(alias) < 0) {
91 connectedDBs.push(alias);
92 mysql.set_logger(winston).add(alias, config[alias]);
93 }
94
95 const db = mysql.use(alias);
96
97 tasks.push(callback => {
98 runMeta(db, file, migrationName, callback);
99 });
100 });
101
102 async.series(tasks, (err, results) => {
103 if (err) {
104 console.log('Migration Error', err);
105 } else {
106 results.forEach(result => {
107 console.log('OK:', result);
108 });
109 }
110 });
111
112 function runMeta(db, file, migrationName, callback) {
113 module.exports.createMetaDataTable(db).then(() => {
114 return module.exports.findMeta(db, migrationName);
115 }).then(meta => {
116 if (meta.length === 0) {
117 console.log(`Executing '${migrationName}':`, file.run);
118 return module.exports.executeSQL(db, file.run).then(() => {
119 return module.exports.createMeta(db, migrationName);
120 });
121 } else {
122 return meta[0];
123 }
124 }).then(() => {
125 callback(undefined, migrationName);
126 }).catch(err => {
127 callback(err);
128 });
129 }
130 },
131
132 undo(env) {
133 console.log(`Reverting: Using ${env} environment`);
134
135 const mysql = require('anytv-node-mysql'),
136 files = module.exports.importFiles(),
137 connectedDBs = [],
138 migrationNames = Object.keys(files).sort((a, b) => {
139 return parseInt(b.split('-')[0]) - parseInt(a.split('-')[0]);
140 });
141
142 undoMeta(migrationNames[0], (err, result) => {
143 if (err) {
144 console.log('Migration Error', err);
145 } else if (result){
146 console.log('OK:', result);
147 } else {
148 console.log('Nothing was reverted.');
149 }
150
151 });
152
153 function undoMeta(migrationName, callback) {
154 const file = files[migrationName],
155 alias = file.dbAlias;
156
157 if (connectedDBs.indexOf(alias) < 0) {
158 connectedDBs.push(alias);
159 mysql.set_logger(winston).add(alias, config[alias]);
160 }
161
162 const db = mysql.use(alias);
163
164 return module.exports.findMeta(db, migrationName).then(meta => {
165 if (meta.length > 0) {
166 console.log(`Executing '${migrationName}':`, file.undo);
167 return module.exports.executeSQL(db, file.undo).then(() => {
168 return module.exports.deleteMeta(db, meta[0].name).then(() => {
169 return meta[0].name;
170 })
171 });
172 } else {
173 return undefined;
174 }
175 }).then(result => {
176 const index = migrationNames.indexOf(migrationName),
177 nextMigration = migrationNames[index + 1];
178
179 if(result === undefined && nextMigration !== undefined){
180 return undoMeta(nextMigration, callback);
181 } else {
182 callback(undefined, result);
183 }
184 }).catch(err => {
185 callback(err);
186 });
187 }
188
189 },
190
191 help() {
192 console.log('Help\n ' +
193 '--create {name} Creates new migration file.\n ' +
194 '--run Run pending migrations.\n ' +
195 '--undo Revert the last migration run.\n ' +
196 ' --env {environment} Environment to run the migrations. Default: `development`.\n ' +
197 '--help Display this help text.\n ');
198 },
199
200 createMetaDataTable(db, callback){
201 return this.executeSQL(db, `CREATE TABLE IF NOT EXISTS migrationMeta (
202 id INT(11) AUTO_INCREMENT PRIMARY KEY,
203 name VARCHAR(50) NOT NULL
204 ) ENGINE=InnoDB;`, callback);
205 },
206
207 findMeta(db, name, callback){
208 return this.executeSQL(db, `SELECT name FROM migrationMeta WHERE name = '${name}' LIMIT 1`, callback);
209 },
210
211 createMeta(db, name, callback){
212 return this.executeSQL(db, `INSERT INTO migrationMeta (name) VALUES ('${name}');`, callback);
213 },
214
215 deleteMeta(db, name, callback){
216 return this.executeSQL(db, `DELETE FROM migrationMeta WHERE name = '${name}'`, callback);
217 },
218
219 executeSQL(db, query, callback){
220 return new Promise((resolve, reject) => {
221 db.query(query, (err, result, args, last_query) => {
222 if (err) {
223 err.last_query = last_query;
224 reject(err);
225 if (callback) {
226 callback(err, undefined);
227 }
228 } else {
229 resolve(result);
230 if (callback) {
231 callback(undefined, result);
232 }
233 }
234 }).end();
235 });
236 },
237
238 importFiles(){
239 const js = {};
240 fs.readdirSync(migrationFolder).filter(file => {
241 return (file.indexOf('.') !== 0) && (file.slice(-5) === '.json');
242 }).forEach(file => {
243 js[file.split('.')[0]] = require(path.join(migrationFolder, file));
244 });
245 return js;
246 }
247};
248
249main(argv);