@dhyasama/totem-merge 6.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/cli.js +154 -0
- package/config.js +23 -0
- package/index.js +109 -0
- package/lib/changeBSON.js +22 -0
- package/lib/helpers.js +117 -0
- package/lib/organizations.js +408 -0
- package/lib/people.js +739 -0
- package/package.json +30 -0
- package/test/config.js +17 -0
- package/test/index.js +1159 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## Merge two organizations or two people
|
|
2
|
+
|
|
3
|
+
* copies source properties to target
|
|
4
|
+
* will not overwrite properties on target, i.e., first-in wins
|
|
5
|
+
* if a single value property, copies over if not value in target, ignores otherwise
|
|
6
|
+
* if an array, concats and dedupes
|
|
7
|
+
* creates property on target if it exists on source but not target
|
|
8
|
+
* recursive to unlimited depth
|
|
9
|
+
* source is marked as deleted, i.e., soft deleted
|
|
10
|
+
* option to just flag rather than merge which is handy for automated use
|
|
11
|
+
|
|
12
|
+
``` js
|
|
13
|
+
npm install --save @dhyasama/ff-merge
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
``` js
|
|
17
|
+
var Merge = require('@dhyasama/ff-merge');
|
|
18
|
+
var merge = new Merge(mongoose, config);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
``` js
|
|
22
|
+
merge.organizations(targetOrg, sourceOrg, 'some username', function(err, result) {});
|
|
23
|
+
|
|
24
|
+
merge.people(targetPerson, sourcePerson, 'some username', function(err, result) {});
|
|
25
|
+
```
|
package/cli.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var mongoose = require('mongoose');
|
|
4
|
+
var config = require('./config');
|
|
5
|
+
var models = require('@dhyasama/totem-models')(mongoose, config);
|
|
6
|
+
var Merge = require('./index.js');
|
|
7
|
+
var merge = new Merge(mongoose, config);
|
|
8
|
+
var program = require('commander');
|
|
9
|
+
var async = require('async');
|
|
10
|
+
var Organization = mongoose.model('Organization');
|
|
11
|
+
var Person = mongoose.model('Person');
|
|
12
|
+
|
|
13
|
+
mongoose.connection.on('error', function(err) {
|
|
14
|
+
done(err);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
mongoose.connect(config.db.uri, function (err, res) {
|
|
18
|
+
|
|
19
|
+
if (err) { done(err); }
|
|
20
|
+
else {
|
|
21
|
+
|
|
22
|
+
console.log('Connected to ' + config.db.uri.substring(config.db.uri.indexOf('@') + 1) + '\n');
|
|
23
|
+
|
|
24
|
+
buildProgram();
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// npm start p 5637a8c0281347cc0bbfca30 5637a8c0281347cc0bbfca3e jason
|
|
31
|
+
// npm start o 5627c370edff044dd5c129ef 5a81b38ceba193f2e7adbc7d jason
|
|
32
|
+
|
|
33
|
+
var buildProgram = function buildProgram() {
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.version('0.0.1')
|
|
37
|
+
.description('Merge people and organizations');
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('people <target> <source> <username>')
|
|
41
|
+
.alias('p')
|
|
42
|
+
.description('Merge two people. The source will be merged into the target')
|
|
43
|
+
.action(function (target, source, username) {
|
|
44
|
+
|
|
45
|
+
username = username ? username.toLowerCase() : '';
|
|
46
|
+
|
|
47
|
+
if (!validate(target, source, username)) { done(); }
|
|
48
|
+
|
|
49
|
+
console.log('Getting people...');
|
|
50
|
+
|
|
51
|
+
async.parallel([
|
|
52
|
+
Person.getById.bind(Person, target),
|
|
53
|
+
Person.getById.bind(Person, source)
|
|
54
|
+
], function(err, results) {
|
|
55
|
+
|
|
56
|
+
if (err) { done(err); }
|
|
57
|
+
else if (!results || results.length != 2 || !results[0] || !results[1]) {
|
|
58
|
+
console.log(' Unable to get people. Perhaps one doesn\'t exist?');
|
|
59
|
+
done();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
merge.people(results[0], results[1], username, function(err, result) {
|
|
63
|
+
|
|
64
|
+
if (err) { done(err); }
|
|
65
|
+
|
|
66
|
+
console.log('**********');
|
|
67
|
+
console.log('RESULT');
|
|
68
|
+
console.log(JSON.stringify(result, null, 2));
|
|
69
|
+
|
|
70
|
+
done();
|
|
71
|
+
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
program
|
|
79
|
+
.command('organizations <target> <source> <username>')
|
|
80
|
+
.alias('o')
|
|
81
|
+
.description('Merge two organizations. The source will be merged into the target')
|
|
82
|
+
.action(function (target, source, username) {
|
|
83
|
+
|
|
84
|
+
username = username ? username.toLowerCase() : '';
|
|
85
|
+
|
|
86
|
+
if (!validate(target, source, username)) { done(); }
|
|
87
|
+
|
|
88
|
+
console.log('Getting orgs...');
|
|
89
|
+
|
|
90
|
+
async.parallel([
|
|
91
|
+
Organization.getById.bind(Organization, target, { role: 'none' }),
|
|
92
|
+
Organization.getById.bind(Organization, source, { role: 'none' })
|
|
93
|
+
], function(err, results) {
|
|
94
|
+
|
|
95
|
+
if (err) { done(err); }
|
|
96
|
+
else if (!results || results.length != 2 || !results[0] || !results[1]) {
|
|
97
|
+
console.log('Unable to get orgs');
|
|
98
|
+
done();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log('Merging orgs...')
|
|
102
|
+
|
|
103
|
+
merge.organizations(results[0], results[1], username, function(err, result) {
|
|
104
|
+
|
|
105
|
+
if (err) { done(err); }
|
|
106
|
+
|
|
107
|
+
console.log('**********');
|
|
108
|
+
console.log('RESULT');
|
|
109
|
+
console.log(JSON.stringify(result, null, 2));
|
|
110
|
+
|
|
111
|
+
done();
|
|
112
|
+
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
program.parse(process.argv);
|
|
120
|
+
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
var done = function done(err) {
|
|
124
|
+
|
|
125
|
+
if (err) { console.error(err); }
|
|
126
|
+
|
|
127
|
+
console.log('DONE');
|
|
128
|
+
|
|
129
|
+
process.exit();
|
|
130
|
+
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
var validate = function validate(target, source, username) {
|
|
134
|
+
|
|
135
|
+
var valid = true;
|
|
136
|
+
|
|
137
|
+
if (!mongoose.Types.ObjectId.isValid(target)) {
|
|
138
|
+
console.log('target is not a valid objectid');
|
|
139
|
+
valid = false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!mongoose.Types.ObjectId.isValid(source)) {
|
|
143
|
+
console.log('source is not a valid objectid');
|
|
144
|
+
valid = false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (username != 'steven' && username != 'jason') {
|
|
148
|
+
console.log('username is not valid');
|
|
149
|
+
valid = false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return valid;
|
|
153
|
+
|
|
154
|
+
};
|
package/config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
|
|
3
|
+
USERNAME: 'jason',
|
|
4
|
+
|
|
5
|
+
CUSTOMER_ID: "5a95ac2ec991f858321c19d6",
|
|
6
|
+
|
|
7
|
+
LPS_ENABLED: false,
|
|
8
|
+
|
|
9
|
+
suppressDebugLog: true,
|
|
10
|
+
|
|
11
|
+
// db: {
|
|
12
|
+
// uri: 'mongodb://ffvc:ytnn5m7A9snIJSOLRqwo7Bpo2QkyYx6q@ds043952.mongolab.com:43952/ff-platform-test'
|
|
13
|
+
// },
|
|
14
|
+
|
|
15
|
+
db: {
|
|
16
|
+
uri: 'mongodb://jason:T576G150HPXLA5q4BJ2o2zj747B5030x@development-shard-00-00-gv0f3.mongodb.net:27017,development-shard-00-01-gv0f3.mongodb.net:27017,development-shard-00-02-gv0f3.mongodb.net:27017/development?ssl=true&replicaSet=Development-shard-0&authSource=admin'
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
crypto: {
|
|
20
|
+
key: 'DUM'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
};
|
package/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var organizations = require('./lib/organizations');
|
|
4
|
+
var people = require('./lib/people');
|
|
5
|
+
var Logger = require('@dhyasama/totem-logger');
|
|
6
|
+
|
|
7
|
+
// Constructor
|
|
8
|
+
var Merge = function(mongoose, config) {
|
|
9
|
+
|
|
10
|
+
this.env = process.env.NODE_ENV || 'development';
|
|
11
|
+
this.logger = new Logger(config.logentries);
|
|
12
|
+
this.CapTable = mongoose.model('CapTable');
|
|
13
|
+
this.Deal = mongoose.model('Deal');
|
|
14
|
+
this.Document = mongoose.model('Document');
|
|
15
|
+
this.Financials = mongoose.model('Financials');
|
|
16
|
+
this.Flag = mongoose.model('Flag');
|
|
17
|
+
this.Interaction = mongoose.model('Interaction');
|
|
18
|
+
this.LimitedPartner = mongoose.model('LimitedPartner');
|
|
19
|
+
this.List = mongoose.model('List');
|
|
20
|
+
this.Message = mongoose.model('Message');
|
|
21
|
+
this.News = mongoose.model('News');
|
|
22
|
+
this.Note = mongoose.model('Note');
|
|
23
|
+
this.Organization = mongoose.model('Organization');
|
|
24
|
+
this.Person = mongoose.model('Person');
|
|
25
|
+
this.Round = mongoose.model('Round');
|
|
26
|
+
this.Snapshot = mongoose.model('Snapshot');
|
|
27
|
+
|
|
28
|
+
// can choose to flag an entity as needing to be merged rather than actually merging it
|
|
29
|
+
// particularly useful for automated processes, e.g., a batch import that detects dupes
|
|
30
|
+
// but you don't want to auto-merge 1000 records
|
|
31
|
+
// defaults to false
|
|
32
|
+
this.flagOnly = config.flag == true ? true : false;
|
|
33
|
+
|
|
34
|
+
// turn on debug if in development and not explicitly turned off
|
|
35
|
+
mongoose.set('debug', (!config.suppressDebugLog && this.env == 'development'));
|
|
36
|
+
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
//////////////////////////////////////////////
|
|
40
|
+
|
|
41
|
+
Merge.prototype.organizations = function(target, source, user, customerId, cb) {
|
|
42
|
+
organizations.merge(this, target, source, user, customerId, cb);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
Merge.prototype.organizationsById = function(targetId, sourceId, user, customerId, cb) {
|
|
46
|
+
|
|
47
|
+
var self = this;
|
|
48
|
+
|
|
49
|
+
self.logger.writeToInfoLog('Merge org ' + sourceId + ' into ' + targetId);
|
|
50
|
+
|
|
51
|
+
self.Organization.getById(targetId, { CUSTOMER_ID: customerId, isWorkerProcess: true }, function(err, target) {
|
|
52
|
+
|
|
53
|
+
if (err) { return cb(err, null); }
|
|
54
|
+
else if (!target) { return cb(new Error('target not found'), null); }
|
|
55
|
+
|
|
56
|
+
self.logger.writeToInfoLog(' Found target ' + target.name);
|
|
57
|
+
|
|
58
|
+
self.Organization.getById(sourceId, { CUSTOMER_ID: customerId, isWorkerProcess: true }, function(err, source) {
|
|
59
|
+
|
|
60
|
+
if (err) { return cb(err, null); }
|
|
61
|
+
else if (!source) { return cb(new Error('source not found'), null); }
|
|
62
|
+
|
|
63
|
+
self.logger.writeToInfoLog(' Found source ' + source.name);
|
|
64
|
+
self.logger.writeToInfoLog(' Merging...');
|
|
65
|
+
|
|
66
|
+
organizations.merge(self, target, source, user, customerId, cb);
|
|
67
|
+
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
Merge.prototype.people = function(target, source, user, customerId, cb) {
|
|
75
|
+
people.merge(this, target, source, user, customerId, cb);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
Merge.prototype.peopleById = function(targetId, sourceId, user, customerId, cb) {
|
|
79
|
+
|
|
80
|
+
var self = this;
|
|
81
|
+
|
|
82
|
+
self.logger.writeToInfoLog('Merge person ' + sourceId + ' into ' + targetId);
|
|
83
|
+
|
|
84
|
+
self.Person.getById(targetId, { CUSTOMER_ID: customerId, isWorkerProcess: true }, function(err, target) {
|
|
85
|
+
|
|
86
|
+
if (err) { return cb(err, null); }
|
|
87
|
+
else if (!target) { return cb(new Error('target not found'), null); }
|
|
88
|
+
|
|
89
|
+
console.log(' Found target', target.name.full);
|
|
90
|
+
console.log(' Target sources:', target.sources.length);
|
|
91
|
+
|
|
92
|
+
self.Person.getById(sourceId, { CUSTOMER_ID: customerId, isWorkerProcess: true }, function(err, source) {
|
|
93
|
+
|
|
94
|
+
if (err) { return cb(err, null); }
|
|
95
|
+
else if (!source) { return cb(new Error('source not found'), null); }
|
|
96
|
+
|
|
97
|
+
self.logger.writeToInfoLog(' Found source ' + source.name.full);
|
|
98
|
+
self.logger.writeToInfoLog(' Source sources ' + source.sources.length);
|
|
99
|
+
self.logger.writeToInfoLog(' Merging...');
|
|
100
|
+
|
|
101
|
+
people.merge(self, target, source, user, customerId, cb);
|
|
102
|
+
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
module.exports = Merge;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var filePath = './node_modules/mongoose-post-find/node_modules/bson/ext/index.js';
|
|
5
|
+
var config = require('./../config');
|
|
6
|
+
var Logger = require('@dhyasama/totem-logger');
|
|
7
|
+
var logger = new Logger(config.logentries);
|
|
8
|
+
|
|
9
|
+
fs.readFile(filePath, 'utf8', function (err, data) {
|
|
10
|
+
|
|
11
|
+
if (err) { logger.handleErr(err); }
|
|
12
|
+
else {
|
|
13
|
+
|
|
14
|
+
var result = data.replace("bson = require('../build/Release/bson');", "bson = require('bson');");
|
|
15
|
+
|
|
16
|
+
fs.writeFile(filePath, result, 'utf8', function (err) {
|
|
17
|
+
if (err) { logger.handleErr(err); }
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
});
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _ = require('underscore');
|
|
4
|
+
var utilities = require('@dhyasama/ffvc-utilities');
|
|
5
|
+
var phoneFormatter = require('phone-formatter');
|
|
6
|
+
|
|
7
|
+
var createSnapshot = module.exports.createSnapshot = function createSnapshot(context, collectionName, doc, createdBy, cb) {
|
|
8
|
+
|
|
9
|
+
context.logger.writeToInfoLog('Creating snapshot - ' + collectionName + ' - ' + createdBy + ' - ' + doc._id);
|
|
10
|
+
|
|
11
|
+
var snapshot = new context.Snapshot({
|
|
12
|
+
createdBy: createdBy,
|
|
13
|
+
collectionName: collectionName,
|
|
14
|
+
doc: doc
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
context.Snapshot.upsert(snapshot, cb);
|
|
18
|
+
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Generic, recursive object merging
|
|
22
|
+
var mergeObjects = function mergeObjects(target, source, doNotMergeKeys) {
|
|
23
|
+
|
|
24
|
+
if (source == 'undefined') return;
|
|
25
|
+
|
|
26
|
+
var jsonSource = JSON.parse(JSON.stringify(source));
|
|
27
|
+
|
|
28
|
+
for (var key in jsonSource) {
|
|
29
|
+
|
|
30
|
+
if (doNotMergeKeys.indexOf(key) >= 0) continue;
|
|
31
|
+
|
|
32
|
+
var sourceItem = source[key];
|
|
33
|
+
if (!sourceItem) continue;
|
|
34
|
+
|
|
35
|
+
var type = typeOf(target[key]);
|
|
36
|
+
if (type == 'null') type = typeOf(source[key]);
|
|
37
|
+
|
|
38
|
+
if (type == 'object') {
|
|
39
|
+
mergeObjects(target[key], sourceItem, doNotMergeKeys);
|
|
40
|
+
}
|
|
41
|
+
else if (type === 'array') {
|
|
42
|
+
target[key] = target[key] || [];
|
|
43
|
+
target[key] = unique(target[key].concat(sourceItem), key);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (target[key] === null) {
|
|
47
|
+
target[key] = sourceItem;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return target;
|
|
54
|
+
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
var removeEntity = module.exports.removeEntity = function removeEntity(context, entity, cb) {
|
|
58
|
+
|
|
59
|
+
context.logger.writeToInfoLog('Removing entity - ' + entity._id.toString());
|
|
60
|
+
|
|
61
|
+
entity.remove(cb);
|
|
62
|
+
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Used for determining if need to dive in recursively
|
|
66
|
+
var typeOf = function typeOf(input) {
|
|
67
|
+
return ({}).toString.call(input).slice(8, -1).toLowerCase();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Deduping with some type support
|
|
71
|
+
var unique = function unique(array, key) {
|
|
72
|
+
|
|
73
|
+
// translate each of these to "company"
|
|
74
|
+
var companyKeys = [
|
|
75
|
+
'first',
|
|
76
|
+
'second',
|
|
77
|
+
'boards',
|
|
78
|
+
'leadership',
|
|
79
|
+
'pcs',
|
|
80
|
+
'investments'
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
// for arrays of objects, specify the object key to use for comparison
|
|
84
|
+
if (key == 'people') key = 'person';
|
|
85
|
+
else if (key == 'phone') key = 'number';
|
|
86
|
+
else if (companyKeys.indexOf(key) >= 0) key = 'company';
|
|
87
|
+
else if (key == 'orgs') key = 'org';
|
|
88
|
+
else if (key == 'investedIn') key = 'id';
|
|
89
|
+
|
|
90
|
+
array = _.compact(array);
|
|
91
|
+
|
|
92
|
+
var result = _.uniq(array, false, function(item) {
|
|
93
|
+
|
|
94
|
+
var val;
|
|
95
|
+
|
|
96
|
+
if (utilities.isValidObjectId(item.toString())) {
|
|
97
|
+
val = item;
|
|
98
|
+
}
|
|
99
|
+
else if (typeOf(item) == 'object') {
|
|
100
|
+
val = key == 'number' ? phoneFormatter.normalize(item[key]) : item[key];
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
val = key == 'number' ? phoneFormatter.normalize(item) : item;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// take one last shot
|
|
107
|
+
if (typeof val == 'undefined') val = item['_id'];
|
|
108
|
+
|
|
109
|
+
return (val || '').toString();
|
|
110
|
+
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return result;
|
|
114
|
+
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports.mergeObjects = mergeObjects;
|