@dhyasama/totem-models 12.18.0 → 12.20.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 +1 -0
- package/lib/Meeting.js +4 -4
- package/lib/Overview.js +160 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -74,6 +74,7 @@ var bootstrap = function(mongoose, config) {
|
|
|
74
74
|
require('./lib/Message.js')(mongoose, config);
|
|
75
75
|
require('./lib/MessageRecipient.js')(mongoose, config);
|
|
76
76
|
require('./lib/News.js')(mongoose, config);
|
|
77
|
+
require('./lib/Overview.js')(mongoose, config);
|
|
77
78
|
require('./lib/Rate.js')(mongoose, config);
|
|
78
79
|
require('./lib/Snapshot.js')(mongoose, config);
|
|
79
80
|
require('./lib/Sync.js')(mongoose, config);
|
package/lib/Meeting.js
CHANGED
|
@@ -64,7 +64,7 @@ module.exports = function(mongoose, config) {
|
|
|
64
64
|
let query = this.findOne({ '_id': id, customer: options.CUSTOMER_ID });
|
|
65
65
|
if (options.populate === true) { query.populate({ path: 'people', select: 'name title avatarUrl doNotDisplay' }); }
|
|
66
66
|
|
|
67
|
-
const result = await query.lean();
|
|
67
|
+
const result = await query.lean({ virtuals: true });
|
|
68
68
|
return result;
|
|
69
69
|
};
|
|
70
70
|
|
|
@@ -84,7 +84,7 @@ module.exports = function(mongoose, config) {
|
|
|
84
84
|
let query = this.find({ 'eventId': eventId, customer: options.CUSTOMER_ID });
|
|
85
85
|
if (options.populate === true) { query.populate({ path: 'people', select: 'name title avatarUrl doNotDisplay' }); }
|
|
86
86
|
|
|
87
|
-
const result = await query.lean();
|
|
87
|
+
const result = await query.lean({ virtuals: true });
|
|
88
88
|
return result;
|
|
89
89
|
};
|
|
90
90
|
|
|
@@ -104,7 +104,7 @@ module.exports = function(mongoose, config) {
|
|
|
104
104
|
let query = this.findOne({ 'uniqueId': uniqueId, customer: options.CUSTOMER_ID });
|
|
105
105
|
if (options.populate === true) { query.populate({ path: 'people', select: 'name title avatarUrl doNotDisplay' }); }
|
|
106
106
|
|
|
107
|
-
const result = await query.lean();
|
|
107
|
+
const result = await query.lean({ virtuals: true });
|
|
108
108
|
return result;
|
|
109
109
|
};
|
|
110
110
|
|
|
@@ -139,7 +139,7 @@ module.exports = function(mongoose, config) {
|
|
|
139
139
|
|
|
140
140
|
if (options.populate === true) { query.populate({ path: 'people', select: 'name title avatarUrl doNotDisplay' }); }
|
|
141
141
|
|
|
142
|
-
const result = await query.lean();
|
|
142
|
+
const result = await query.lean({ virtuals: true });
|
|
143
143
|
return result;
|
|
144
144
|
};
|
|
145
145
|
|
package/lib/Overview.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
module.exports = function(mongoose, config) {
|
|
4
|
+
|
|
5
|
+
let Schema = mongoose.Schema;
|
|
6
|
+
|
|
7
|
+
// Set of models an overview can be generated for. Mirrors the polymorphic
|
|
8
|
+
// entity pattern used elsewhere in the codebase (Folder, Note, etc.).
|
|
9
|
+
const ALLOWED_ENTITY_MODELS = ['Organization', 'Fund', 'LimitedPartner'];
|
|
10
|
+
|
|
11
|
+
let Overview = new Schema({
|
|
12
|
+
|
|
13
|
+
customer: { type: Schema.ObjectId, ref: 'Account', index: true, required: true },
|
|
14
|
+
|
|
15
|
+
// What this overview is *about*. The model picks which loader runs to
|
|
16
|
+
// collect source material (org has emails/meetings/financials; fund has
|
|
17
|
+
// portfolio rollups; LP has communications/campaigns; etc.).
|
|
18
|
+
entity: {
|
|
19
|
+
// refPath lets `.populate('entity.id')` resolve to the correct
|
|
20
|
+
// collection based on the sibling `entity.model` field.
|
|
21
|
+
id: { type: Schema.ObjectId, required: true, refPath: 'entity.model' },
|
|
22
|
+
model: { type: String, enum: ALLOWED_ENTITY_MODELS, required: true }
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// Lifecycle: 'generating' while the Lambda is producing the next overview,
|
|
26
|
+
// 'ready' once new content lands, 'error' if generation failed. Any prior
|
|
27
|
+
// 'ready' html is preserved while regenerating.
|
|
28
|
+
status: { type: String, enum: ['generating', 'ready', 'error'], default: 'generating', required: true, index: true },
|
|
29
|
+
|
|
30
|
+
// The AI-generated overview, as sanitized HTML. The model picks the
|
|
31
|
+
// layout, sections, ordering, and visualizations that best tell the story
|
|
32
|
+
// of this particular entity. May include <canvas data-chart='{...}'>
|
|
33
|
+
// blocks that the client hydrates with Chart.js.
|
|
34
|
+
html: { type: String },
|
|
35
|
+
|
|
36
|
+
// When the latest 'ready' html was produced.
|
|
37
|
+
generatedOn: { type: Date }
|
|
38
|
+
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// One overview per (customer, entity). Generations upsert in place.
|
|
42
|
+
Overview.index({ customer: 1, 'entity.id': 1, 'entity.model': 1 }, { unique: true });
|
|
43
|
+
|
|
44
|
+
const validateEntity = function(entity) {
|
|
45
|
+
if (!entity || typeof entity !== 'object') throw new Error('entity is required');
|
|
46
|
+
if (!entity.id) throw new Error('entity.id is required');
|
|
47
|
+
if (!mongoose.Types.ObjectId.isValid(entity.id)) throw new Error('entity.id is not a valid ObjectId');
|
|
48
|
+
if (!entity.model) throw new Error('entity.model is required');
|
|
49
|
+
if (ALLOWED_ENTITY_MODELS.indexOf(entity.model) === -1) {
|
|
50
|
+
throw new Error('entity.model must be one of: ' + ALLOWED_ENTITY_MODELS.join(', '));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const entityFilter = function(customerId, entity) {
|
|
55
|
+
return {
|
|
56
|
+
customer: customerId,
|
|
57
|
+
'entity.id': entity.id,
|
|
58
|
+
'entity.model': entity.model
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
Overview.statics.getByEntity = function(customerId, entity, cb) {
|
|
63
|
+
|
|
64
|
+
const run = async () => {
|
|
65
|
+
if (!customerId) { throw new Error('customerId is required'); }
|
|
66
|
+
if (!mongoose.Types.ObjectId.isValid(customerId)) { throw new Error('customerId is not a valid ObjectId'); }
|
|
67
|
+
validateEntity(entity);
|
|
68
|
+
|
|
69
|
+
return await this.findOne(entityFilter(customerId, entity));
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (typeof cb === 'function') { run().then(result => cb(null, result), err => cb(err)); return; }
|
|
73
|
+
else { return run(); }
|
|
74
|
+
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Marks an overview as currently being generated. Creates the record if it
|
|
78
|
+
// doesn't exist. Preserves any existing 'ready' html so the UI keeps
|
|
79
|
+
// showing the previous overview while the new one is in flight.
|
|
80
|
+
Overview.statics.markGenerating = function(customerId, entity, cb) {
|
|
81
|
+
|
|
82
|
+
const run = async () => {
|
|
83
|
+
if (!customerId) { throw new Error('customerId is required'); }
|
|
84
|
+
validateEntity(entity);
|
|
85
|
+
|
|
86
|
+
const update = {
|
|
87
|
+
$set: { status: 'generating' },
|
|
88
|
+
$setOnInsert: { customer: customerId, entity: { id: entity.id, model: entity.model } }
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return await this.findOneAndUpdate(
|
|
92
|
+
entityFilter(customerId, entity),
|
|
93
|
+
update,
|
|
94
|
+
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (typeof cb === 'function') { run().then(result => cb(null, result), err => cb(err)); return; }
|
|
99
|
+
else { return run(); }
|
|
100
|
+
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Writes the finished html + flips status to 'ready'.
|
|
104
|
+
Overview.statics.saveResult = function(customerId, entity, html, cb) {
|
|
105
|
+
|
|
106
|
+
const run = async () => {
|
|
107
|
+
if (!customerId) { throw new Error('customerId is required'); }
|
|
108
|
+
validateEntity(entity);
|
|
109
|
+
if (!html || typeof html !== 'string') { throw new Error('html is required'); }
|
|
110
|
+
|
|
111
|
+
const update = {
|
|
112
|
+
$set: {
|
|
113
|
+
status: 'ready',
|
|
114
|
+
html: html,
|
|
115
|
+
generatedOn: new Date()
|
|
116
|
+
},
|
|
117
|
+
$setOnInsert: { customer: customerId, entity: { id: entity.id, model: entity.model } }
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return await this.findOneAndUpdate(
|
|
121
|
+
entityFilter(customerId, entity),
|
|
122
|
+
update,
|
|
123
|
+
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (typeof cb === 'function') { run().then(result => cb(null, result), err => cb(err)); return; }
|
|
128
|
+
else { return run(); }
|
|
129
|
+
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
Overview.statics.markError = function(customerId, entity, cb) {
|
|
133
|
+
|
|
134
|
+
const run = async () => {
|
|
135
|
+
if (!customerId) { throw new Error('customerId is required'); }
|
|
136
|
+
validateEntity(entity);
|
|
137
|
+
|
|
138
|
+
const update = {
|
|
139
|
+
$set: { status: 'error' },
|
|
140
|
+
$setOnInsert: { customer: customerId, entity: { id: entity.id, model: entity.model } }
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return await this.findOneAndUpdate(
|
|
144
|
+
entityFilter(customerId, entity),
|
|
145
|
+
update,
|
|
146
|
+
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (typeof cb === 'function') { run().then(result => cb(null, result), err => cb(err)); return; }
|
|
151
|
+
else { return run(); }
|
|
152
|
+
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
Overview.set('autoIndex', false);
|
|
156
|
+
Overview.on('index', function(err) { if (err) console.log('error building overview indexes:', err); });
|
|
157
|
+
|
|
158
|
+
mongoose.model('Overview', Overview);
|
|
159
|
+
|
|
160
|
+
};
|