@dhyasama/totem-models 1.0.0

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/index.js ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ var bootstrap = function(mongoose, config) {
4
+
5
+ var env = process.env.NODE_ENV || 'development';
6
+
7
+ // turn on debug if in development and not explicitly turned off
8
+ mongoose.set('debug', (!config.suppressDebugLog && env == 'development'));//mongoose.set('debug',true);
9
+
10
+ // use bluebird promises globally within mongoose
11
+ mongoose.Promise = require('bluebird');
12
+
13
+ // Validate config
14
+ var configHas_LPS_ENABLED = typeof config['LPS_ENABLED'] != 'undefined';
15
+ if (!configHas_LPS_ENABLED) throw new Error('Config is missing LPS_ENABLED setting');
16
+
17
+ var configHas_CUSTOMER_ID = typeof config['CUSTOMER_ID'] != 'undefined';
18
+ if (!configHas_CUSTOMER_ID) throw new Error('Config is missing CUSTOMER_ID setting');
19
+
20
+ require('./lib/Account.js')(mongoose, config);
21
+ require('./lib/Activity.js')(mongoose, config);
22
+ require('./lib/CalendarEvent.js')(mongoose, config);
23
+ require('./lib/Fund.js')(mongoose, config);
24
+ require('./lib/List.js')(mongoose, config);
25
+ require('./lib/News.js')(mongoose, config);
26
+
27
+ // note must be loaded before limited partner
28
+ // note must be loaded before organization
29
+ // note must be loaded before person
30
+ require('./lib/Note.js')(mongoose, config);
31
+
32
+ // organization must be loaded before limited partner
33
+ require('./lib/Organization.js')(mongoose, config);
34
+
35
+ // limited partner must be loaded before person
36
+ require('./lib/LimitedPartner.js')(mongoose, config);
37
+
38
+ require('./lib/Person.js')(mongoose, config);
39
+
40
+ };
41
+
42
+ module.exports = bootstrap;
package/lib/Account.js ADDED
@@ -0,0 +1,312 @@
1
+ "use strict";
2
+
3
+ module.exports = function(mongoose, config) {
4
+
5
+ var
6
+
7
+ Schema = mongoose.Schema,
8
+ Crypto = require('@dhyasama/ffvc-crypto'),
9
+ crypto = new Crypto({'key': config.crypto.key}),
10
+ env = process.env.NODE_ENV || 'development',
11
+ bcrypt = require('bcrypt'),
12
+ _ = require('underscore');
13
+
14
+ var Account = new Schema({
15
+
16
+ username: { type: String, required: true, unique: true },
17
+ password: { type: String, default: '' },
18
+ email: { type: String, required: true, unique: true },
19
+ avatar: { type: String, default: '/media/abstract-cubes.png' },
20
+ reset: {
21
+ token: { type: String, default: '' },
22
+ expiresOn: { type: Date }
23
+ },
24
+ setup: {
25
+ token: { type: String, default: '' },
26
+ expiresOn: { type: Date },
27
+ complete: { type: Boolean, default: false },
28
+ terms: {
29
+ raw: { type: String },
30
+ hash: { type: String }
31
+ }
32
+ },
33
+ twoFactorAuth: {
34
+ enabled: { type: Boolean, default: true },
35
+ phone: { type: String, default: '' },
36
+ country: {
37
+ callingCode: { type: String, default: '' },
38
+ abbreviation: { type: String, default: '' }
39
+ },
40
+ verified: { type: Boolean, default: false }
41
+ },
42
+ admin: { type: Boolean, default: false },
43
+ active: { type: Boolean, default: false },
44
+ subscriber: { type: Boolean, default: false },
45
+ role: { type: String, enum: ['admin', 'lp-full', 'lp-names', 'none'], default: 'none' },
46
+
47
+ // Enable this and iLevel links will show on portfolio pages
48
+ iLevelUser: { type: Boolean, default: false },
49
+
50
+ person: {
51
+ type: Schema.ObjectId,
52
+ ref: 'Person',
53
+ required: true
54
+ },
55
+
56
+ // todo - change to org
57
+ customer: {
58
+ type: Schema.ObjectId,
59
+ ref: 'Investor',
60
+ required: false
61
+ },
62
+
63
+ // todo - port from customer
64
+ org: {
65
+ type: Schema.ObjectId,
66
+ ref: 'Organization',
67
+ required: false
68
+ },
69
+
70
+ auth: {
71
+
72
+ strategy: { type: String, enum: ['google', 'microsoft', 'none'], default: 'none' },
73
+
74
+ google: {
75
+ accessToken: { type: String, default: '' },
76
+ refreshToken: { type: String, default: '' },
77
+ params: { type: Schema.Types.Mixed },
78
+ profile: { type: Schema.Types.Mixed },
79
+ watch: { type: Schema.Types.Mixed },
80
+ nextSyncToken: { type: String, default: null }
81
+ },
82
+
83
+ microsoft: {
84
+ accessToken: { type: String, default: '' },
85
+ refreshToken: { type: String, default: '' },
86
+ params: { type: Schema.Types.Mixed },
87
+ profile: { type: Schema.Types.Mixed },
88
+ watch: { type: Schema.Types.Mixed }
89
+ }
90
+
91
+ },
92
+
93
+ calendar: {
94
+ active: { type: Boolean, default: false }, // only pull if this is active
95
+ forcePullAll: { type: Boolean, default: false } // override newest event date as min and go back all the way
96
+ }
97
+
98
+ });
99
+
100
+ Account.methods.phone = function() {
101
+ return this.twoFactorAuth.country.callingCode + this.twoFactorAuth.phone;
102
+ };
103
+
104
+ Account.statics.getById = function (id, cb) {
105
+
106
+ this
107
+ .findById(id)
108
+ .populate('person org')
109
+ .exec(function (err, account) {
110
+ if (err) {
111
+ return cb(err, null);
112
+ }
113
+ else if (account && account.person) {
114
+ account.deepPopulate('person.lps.lp', function (err, result) {
115
+ return cb(err, account);
116
+ });
117
+ }
118
+ else if (!account) {
119
+ return cb(new Error('No account with id ' + id.toString()), null);
120
+ }
121
+ else if (account && !account.person) {
122
+ return cb(new Error('Missing person reference on account with id ' + id.toString()), null);
123
+ }
124
+ else {
125
+ return cb(new Error('Unknown error'), null);
126
+ }
127
+ });
128
+
129
+ };
130
+
131
+ Account.statics.getByPersonIds = function (ids, cb) {
132
+ this
133
+ .find({ 'person': { $in : ids } })
134
+ .populate('person', 'name')
135
+ .exec(cb);
136
+ };
137
+
138
+ Account.statics.upsert = function(account, cb) {
139
+ account.save(cb);
140
+ };
141
+
142
+ Account.statics.findByUsername = function(username, cb) {
143
+ this
144
+ .findOne({ 'username': new RegExp(username, 'i') })
145
+ .populate('person')
146
+ .exec(function (err, account) {
147
+ cb(err, account);
148
+ });
149
+ };
150
+
151
+ Account.statics.findByEmail = function(email, cb) {
152
+ this
153
+ .findOne({ 'email': email})
154
+ .populate('person')
155
+ .exec(function (err, account) {
156
+ cb(err, account);
157
+ });
158
+ };
159
+
160
+ Account.statics.findByffEmail = function(email, cb) {
161
+ email = email.toLowerCase();
162
+
163
+ if (email.indexOf('@ffvc.com') > -1) {
164
+ this
165
+ .findOne({ 'email': email})
166
+ .populate('person')
167
+ .exec(function (err, account) {
168
+ cb(err, account);
169
+ });
170
+ } else {
171
+ //passing in null err so passport hits failureRedirect: '/login'
172
+ cb(null, null);
173
+ }
174
+ };
175
+
176
+ Account.statics.findByResetToken = function(token, cb) {
177
+ this
178
+ .findOne({ 'reset.token': token})
179
+ .populate('person')
180
+ .exec(function (err, account) {
181
+ cb(err, account);
182
+ });
183
+ };
184
+
185
+ Account.statics.findBySetupToken = function(token, cb) {
186
+ this
187
+ .findOne({ 'setup.token': token})
188
+ .populate('person')
189
+ .exec(function (err, account) {
190
+ cb(err, account);
191
+ });
192
+ };
193
+
194
+ Account.statics.findByCredentials = function (username, cleartextPassword, cb) {
195
+
196
+ this
197
+ .findOne({ 'username': new RegExp(username, 'i') })
198
+ .populate('person')
199
+ .exec(function (err, account) {
200
+
201
+ if (err) {
202
+ return cb(err, null);
203
+ }
204
+ else if (account != null) {
205
+
206
+ // found user, now check password
207
+
208
+ bcrypt.compare(cleartextPassword, account.password, function(err, res) {
209
+ if (err) { return cb(err, null); } // error
210
+ else if (res) { return cb(null, account); } // correct password
211
+ else { return cb(null, null); } // wrong password
212
+ });
213
+
214
+ }
215
+ else {
216
+ return cb(null, null); // user not found
217
+ }
218
+
219
+ });
220
+
221
+ };
222
+
223
+ Account.statics.listGoogleAccounts = function(cb) {
224
+
225
+ this
226
+ .find({
227
+ 'active': true,
228
+ 'calendar.active': true,
229
+ 'auth.strategy': 'google',
230
+ 'auth.google.refreshToken': { $ne: null }
231
+ })
232
+ .exec(cb);
233
+
234
+ };
235
+
236
+ Account.statics.listAllAccounts = function (cb) {
237
+
238
+ // sort is done post retrieval because query sort is done pre-decryption
239
+
240
+ this
241
+ .find({'active': true})
242
+ .populate('person')
243
+ .exec(function (err, accounts) {
244
+ return cb(err, _.sortBy(accounts, function(account) {
245
+ if (account.person && account.person.name && account.person.name.last) {
246
+ return account.person.name.last;
247
+ }
248
+ else return '';
249
+ }));
250
+ });
251
+
252
+ };
253
+
254
+ Account.statics.listAllAccountsSkinny = function (cb) {
255
+
256
+ // sort is done post retrieval because query sort is done pre-decryption
257
+ this
258
+ .find({'active': true})
259
+ .exec(cb);
260
+
261
+ };
262
+
263
+ //Account.pre('save', function(next) {
264
+ //
265
+ // // Encrypt sensitive data
266
+ //
267
+ // if (this.auth) {
268
+ // if (this.auth.google) {
269
+ // if (this.auth.google.accessToken) this.auth.google.accessToken = crypto.encrypt(this.auth.google.accessToken);
270
+ // if (this.auth.google.refreshToken) this.auth.google.refreshToken = crypto.encrypt(this.auth.google.refreshToken);
271
+ // if (this.auth.google.params) {
272
+ // if (this.auth.google.params.access_token) this.auth.google.params.access_token = crypto.encrypt(this.auth.google.params.access_token);
273
+ // if (this.auth.google.params.id_token) this.auth.google.params.id_token = crypto.encrypt(this.auth.google.params.id_token);
274
+ // }
275
+ // }
276
+ // }
277
+ //
278
+ // next();
279
+ //
280
+ //});
281
+ //
282
+ //Account.post('init', function() {
283
+ //
284
+ // // Decrypt sensitive data
285
+ //
286
+ // if (this.auth) {
287
+ // if (this.auth.google) {
288
+ // if (this.auth.google.accessToken) this.auth.google.accessToken = crypto.decrypt(this.auth.google.accessToken);
289
+ // if (this.auth.google.refreshToken) this.auth.google.refreshToken = crypto.decrypt(this.auth.google.refreshToken);
290
+ // if (this.auth.google.params) {
291
+ // if (this.auth.google.params.access_token) this.auth.google.params.access_token = crypto.decrypt(this.auth.google.params.access_token);
292
+ // if (this.auth.google.params.id_token) this.auth.google.params.id_token = crypto.decrypt(this.auth.google.params.id_token);
293
+ // }
294
+ // }
295
+ // }
296
+ //
297
+ //});
298
+
299
+ Account.post('remove', function(doc) {
300
+ // CalendarEvent.account
301
+ // Person.account
302
+ });
303
+
304
+ Account.set('autoIndex', true);
305
+ Account.on('index', function(err) { console.log('error building account indexes: ' + err); });
306
+
307
+ var deepPopulate = require('mongoose-deep-populate')(mongoose);
308
+ Account.plugin(deepPopulate);
309
+
310
+ mongoose.model('Account', Account);
311
+
312
+ };
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+
3
+ module.exports = function(mongoose, config) {
4
+
5
+ var
6
+
7
+ Schema = mongoose.Schema,
8
+ env = process.env.NODE_ENV || 'development',
9
+ Geoip = require('@dhyasama/ffvc-geoip'),
10
+ geoip = new Geoip(),
11
+ async = require('async'),
12
+ rangeCheck = require('range_check');
13
+
14
+ var Activity = new Schema({
15
+
16
+ username: { type: String, required: true, index: true },
17
+
18
+ items: [{
19
+
20
+ type: {
21
+ type: String,
22
+ required: true,
23
+ enum: [
24
+ 'SETUP ACCOUNT',
25
+ 'LOGIN SUCCESS',
26
+ 'LOGIN FAILURE',
27
+ 'NEW DEVICE',
28
+ 'PASSWORD RESET',
29
+ 'PASSWORD CHANGE',
30
+ 'DOCUMENT DOWNLOAD',
31
+ 'ACCOUNT CHANGE'
32
+ ]
33
+ },
34
+
35
+ timestamp: { type: Date },
36
+
37
+ remoteAddress: { type: String },
38
+
39
+ geolocation: {},
40
+
41
+ document: {
42
+ url: { type: String },
43
+ filename: { type: String }
44
+ }
45
+
46
+ }]
47
+
48
+ });
49
+
50
+
51
+
52
+ Activity.statics.add = function(username, type, remoteAddress, url, filename, callback) {
53
+
54
+ var self = this;
55
+
56
+ if (!username) throw new Error('username is required');
57
+ if (!type) throw new Error('type is required');
58
+ if (!callback) throw new Error('callback is required');
59
+
60
+ username = username.toLowerCase();
61
+
62
+ var getActivity = function(cb) {
63
+
64
+ self.findOne({ 'username': username }, function(err, activity) {
65
+
66
+ if (err) return cb(err, null);
67
+
68
+ if (!activity) {
69
+ var A = mongoose.model('Activity');
70
+ activity = new A();
71
+ activity.username = username;
72
+ }
73
+
74
+ return cb(null, activity);
75
+
76
+ });
77
+
78
+ };
79
+
80
+ var getGeolocation = function(cb) {
81
+ if (!rangeCheck.validIp(remoteAddress)) return cb(null, {});
82
+ geoip.locate(remoteAddress, cb);
83
+ };
84
+
85
+ var asyncFinally = function(err, results) {
86
+
87
+ var activity = results ? results[0] : {};
88
+ var geolocation = results ? results[1] : {};
89
+
90
+ var item = {
91
+ type: type,
92
+ timestamp: new Date(),
93
+ geolocation: geolocation
94
+ };
95
+
96
+ if (url) {
97
+ item.document = {
98
+ url: url,
99
+ filename: filename
100
+ };
101
+ }
102
+
103
+ if (activity) {
104
+ activity.items.unshift(item);
105
+ activity.save(function(err, result) {
106
+ return callback(err, item);
107
+ });
108
+ }
109
+ else {
110
+ return callback(null, null);
111
+ }
112
+
113
+ };
114
+
115
+ async.parallel([getActivity, getGeolocation], asyncFinally);
116
+
117
+ };
118
+
119
+ Activity.statics.forMessage = function(username, messageId, cb) {
120
+ this.find({ 'username': username.toLowerCase(), 'items.document.messageId': messageId }, cb);
121
+ };
122
+
123
+ Activity.statics.list = function(username, maxItems, cb) {
124
+
125
+ var query = this.findOne({ 'username': username.toLowerCase() });
126
+
127
+ if (maxItems && maxItems >= 1) { query = query.slice('items', maxItems); }
128
+
129
+ query.exec(cb);
130
+
131
+ };
132
+
133
+ Activity.set('autoIndex', true);
134
+ Activity.on('index', function(err) { console.log('error building activity indexes: ' + err); });
135
+
136
+ mongoose.model('Activity', Activity);
137
+
138
+ };
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+
3
+ module.exports = function(mongoose, config) {
4
+
5
+ var
6
+
7
+ Schema = mongoose.Schema,
8
+ env = process.env.NODE_ENV || 'development',
9
+ _ = require('underscore');
10
+
11
+ var CalendarEvent = new Schema({
12
+
13
+ account: { type: Schema.ObjectId, ref: 'Account', index: true, required: true },
14
+
15
+ email: { type: String, index: true, required: true },
16
+
17
+ provider: { type: String, index: false, required: true },
18
+
19
+ eventId: { type: String, index: true, required: true },
20
+
21
+ status: { type: String, index: true, required: true },
22
+
23
+ summary: { type: String, index: false, required: true },
24
+
25
+ start: { type: Date, index: false, required: true },
26
+
27
+ end: { type: Date, index: false, required: true },
28
+
29
+ attendees: [{
30
+
31
+ email: { type: String, index: true, required: true },
32
+
33
+ displayName: { type: String, index: true, required: true }
34
+
35
+ }],
36
+
37
+ allInternal: { type: Boolean, index: true, required: true },
38
+
39
+ processed: { type: Boolean, index: true, required: true },
40
+
41
+ createdOn: { type: Date, index: false, required: true },
42
+
43
+ updatedOn: { type: Date, index: false, required: true },
44
+
45
+ importTransactionId: { type: String, index: true, required: false },
46
+
47
+ raw: { type: String, index: false, required: true }
48
+
49
+ });
50
+
51
+ CalendarEvent.virtual('original').get(function () {
52
+ return JSON.parse(this.raw);
53
+ });
54
+
55
+ CalendarEvent.statics.findByAccountId = function (accountId, cb) {
56
+ this
57
+ .find({ account: accountId })
58
+ .exec(cb);
59
+ };
60
+
61
+ CalendarEvent.statics.findByCreatedRange = function (since, until, cb) {
62
+ this
63
+ .find({ createdOn: { $gt: since, $lt: since } })
64
+ .exec(cb);
65
+ };
66
+
67
+ CalendarEvent.statics.findByCreatedSince = function (since, cb) {
68
+ this
69
+ .find({ createdOn: { $gt: since } })
70
+ .exec(cb);
71
+ };
72
+
73
+ CalendarEvent.statics.findByEmail = function (email, cb) {
74
+ this
75
+ .find({ email: email })
76
+ .exec(cb);
77
+ };
78
+
79
+ CalendarEvent.statics.findByEventId = function (eventId, cb) {
80
+ this
81
+ .findOne({ 'eventId': eventId })
82
+ .exec(cb);
83
+ };
84
+
85
+ CalendarEvent.statics.findUnprocessedForAccount = function (id, cb) {
86
+ this
87
+ .find({ account: id, processed: false })
88
+ .populate('account', 'email customer')
89
+ .exec(cb);
90
+ };
91
+
92
+ CalendarEvent.statics.findByUsername = function (username, cb) {
93
+ this
94
+ .find({ 'account.username': username })
95
+ .exec(cb);
96
+ };
97
+
98
+ CalendarEvent.statics.getById = function (id, cb) {
99
+ this
100
+ .findOne({ '_id': id })
101
+ .populate('account', 'email customer')
102
+ .exec(cb);
103
+ };
104
+
105
+ CalendarEvent.statics.getNewest = function (email, cb) {
106
+ this
107
+ .findOne({email: email})
108
+ .sort({createdOn:-1})
109
+ .exec(cb);
110
+ };
111
+
112
+ CalendarEvent.statics.upsert = function(calendarEvent, cb) {
113
+
114
+ var self = this;
115
+
116
+ // Prevent dupe events
117
+
118
+ var query = { 'eventId': calendarEvent.eventId };
119
+ var options = { 'upsert': true, 'new': true };
120
+ var update = {
121
+ account: calendarEvent.account,
122
+ email: calendarEvent.email,
123
+ provider: calendarEvent.provider,
124
+ eventId: calendarEvent.eventId,
125
+ status: calendarEvent.status,
126
+ summary: calendarEvent.summary,
127
+ start: calendarEvent.start,
128
+ end: calendarEvent.end,
129
+ attendees: calendarEvent.attendees,
130
+ allInternal: calendarEvent.allInternal,
131
+ processed: calendarEvent.processed,
132
+ createdOn: calendarEvent.createdOn,
133
+ updatedOn: calendarEvent.updatedOn,
134
+ raw: calendarEvent.raw
135
+ };
136
+
137
+ self.findOneAndUpdate(query, update, options, cb);
138
+
139
+ };
140
+
141
+ CalendarEvent.post('remove', function(doc) {
142
+ // Person.calendarEventId
143
+ });
144
+
145
+ CalendarEvent.set('autoIndex', true);
146
+ CalendarEvent.on('index', function(err) { console.log('error building calendar event indexes: ' + err); });
147
+
148
+ mongoose.model('CalendarEvent', CalendarEvent);
149
+
150
+ };