@mongoosejs/studio 0.5.4 → 0.5.6
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/backend/actions/Dashboard/createDashboard.js +12 -2
- package/backend/actions/Dashboard/getDashboard.js +6 -2
- package/backend/actions/Dashboard/getDashboards.js +3 -1
- package/backend/actions/Dashboard/updateDashboard.js +19 -6
- package/backend/actions/Model/analyzeSchema.js +202 -0
- package/backend/actions/Model/index.js +2 -0
- package/backend/actions/Model/validateDocument.js +35 -0
- package/backend/authorize.js +2 -0
- package/backend/db/dashboardSchema.js +31 -0
- package/backend/helpers/validateDocumentWithTimeout.js +39 -0
- package/backend/netlify.js +1 -0
- package/backend/next.js +1 -0
- package/express.js +1 -0
- package/frontend/public/app.js +859 -102
- package/frontend/public/images/pin-off.svg +1 -0
- package/frontend/public/images/pin.svg +1 -0
- package/frontend/public/tw.css +249 -28
- package/frontend/src/api.js +12 -0
- package/frontend/src/array-utils.js +60 -2
- package/frontend/src/dashboards/dashboards.html +52 -4
- package/frontend/src/dashboards/dashboards.js +96 -1
- package/frontend/src/detail-array/detail-array.html +98 -14
- package/frontend/src/detail-array/detail-array.js +73 -3
- package/frontend/src/document/document.html +51 -0
- package/frontend/src/document/document.js +13 -0
- package/frontend/src/document-details/document-details.html +10 -3
- package/frontend/src/document-details/document-details.js +28 -0
- package/frontend/src/document-details/document-property/document-property.html +36 -44
- package/frontend/src/document-details/document-property/document-property.js +78 -14
- package/frontend/src/edit-array/edit-array.html +143 -8
- package/frontend/src/edit-array/edit-array.js +104 -2
- package/frontend/src/models/analyze-schema-modal/analyze-schema-modal.html +169 -0
- package/frontend/src/models/analyze-schema-modal/analyze-schema-modal.js +149 -0
- package/frontend/src/models/models.html +25 -13
- package/frontend/src/models/models.js +5 -13
- package/package.json +2 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const mongoose = require('mongoose');
|
|
5
6
|
|
|
6
7
|
const CreateDashboardParams = new Archetype({
|
|
7
8
|
title: {
|
|
@@ -12,18 +13,27 @@ const CreateDashboardParams = new Archetype({
|
|
|
12
13
|
$type: 'string',
|
|
13
14
|
$required: true
|
|
14
15
|
},
|
|
16
|
+
initiatedById: {
|
|
17
|
+
$type: mongoose.Types.ObjectId,
|
|
18
|
+
$required: false
|
|
19
|
+
},
|
|
15
20
|
roles: {
|
|
16
21
|
$type: ['string']
|
|
17
22
|
}
|
|
18
23
|
}).compile('CreateDashboardParams');
|
|
19
24
|
|
|
20
25
|
module.exports = ({ studioConnection }) => async function createDashboard(params) {
|
|
21
|
-
const { title, code, roles } = new CreateDashboardParams(params);
|
|
26
|
+
const { title, code, initiatedById, roles } = new CreateDashboardParams(params);
|
|
22
27
|
const Dashboard = studioConnection.model('__Studio_Dashboard');
|
|
23
28
|
|
|
24
29
|
await authorize('Dashboard.createDashboard', roles);
|
|
25
30
|
|
|
26
|
-
const dashboard = await Dashboard.create({
|
|
31
|
+
const dashboard = await Dashboard.create({
|
|
32
|
+
title,
|
|
33
|
+
code,
|
|
34
|
+
createdById: initiatedById,
|
|
35
|
+
createdBy: params.initiatedBy
|
|
36
|
+
});
|
|
27
37
|
|
|
28
38
|
return { dashboard };
|
|
29
39
|
};
|
|
@@ -51,6 +51,7 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
51
51
|
return {};
|
|
52
52
|
}
|
|
53
53
|
return completeDashboardEvaluate(
|
|
54
|
+
Dashboard,
|
|
54
55
|
DashboardResult,
|
|
55
56
|
dashboardResult._id,
|
|
56
57
|
null,
|
|
@@ -73,6 +74,7 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
73
74
|
return {};
|
|
74
75
|
}
|
|
75
76
|
return completeDashboardEvaluate(
|
|
77
|
+
Dashboard,
|
|
76
78
|
DashboardResult,
|
|
77
79
|
dashboardResult._id,
|
|
78
80
|
result,
|
|
@@ -97,13 +99,15 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
97
99
|
}
|
|
98
100
|
};
|
|
99
101
|
|
|
100
|
-
async function completeDashboardEvaluate(DashboardResult, dashboardResultId, result, error, status) {
|
|
102
|
+
async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardResultId, result, error, status) {
|
|
101
103
|
const dashboardResult = await DashboardResult.findById(dashboardResultId).orFail();
|
|
102
|
-
|
|
104
|
+
const finishedEvaluatingAt = new Date();
|
|
105
|
+
dashboardResult.finishedEvaluatingAt = finishedEvaluatingAt;
|
|
103
106
|
dashboardResult.result = result;
|
|
104
107
|
dashboardResult.error = error;
|
|
105
108
|
dashboardResult.status = status;
|
|
106
109
|
await dashboardResult.save();
|
|
110
|
+
await Dashboard.updateOne({ _id: dashboardResult.dashboardId }, { $set: { lastEvaluatedAt: finishedEvaluatingAt } });
|
|
107
111
|
return { dashboardResult };
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -15,7 +15,9 @@ module.exports = ({ studioConnection }) => async function getDashboards(params)
|
|
|
15
15
|
|
|
16
16
|
await authorize('Dashboard.getDashboards', roles);
|
|
17
17
|
|
|
18
|
-
const dashboards = await Dashboard
|
|
18
|
+
const dashboards = await Dashboard
|
|
19
|
+
.find()
|
|
20
|
+
.sort({ isPinned: -1, createdAt: -1, _id: -1 });
|
|
19
21
|
|
|
20
22
|
return { dashboards };
|
|
21
23
|
};
|
|
@@ -9,8 +9,7 @@ const UpdateDashboardParams = new Archetype({
|
|
|
9
9
|
$required: true
|
|
10
10
|
},
|
|
11
11
|
code: {
|
|
12
|
-
$type: 'string'
|
|
13
|
-
$required: true
|
|
12
|
+
$type: 'string'
|
|
14
13
|
},
|
|
15
14
|
title: {
|
|
16
15
|
$type: 'string'
|
|
@@ -18,19 +17,26 @@ const UpdateDashboardParams = new Archetype({
|
|
|
18
17
|
description: {
|
|
19
18
|
$type: 'string'
|
|
20
19
|
},
|
|
20
|
+
isPinned: {
|
|
21
|
+
$type: 'boolean'
|
|
22
|
+
},
|
|
21
23
|
roles: {
|
|
22
24
|
$type: ['string']
|
|
23
25
|
}
|
|
24
26
|
}).compile('UpdateDashboardParams');
|
|
25
27
|
|
|
26
28
|
module.exports = ({ studioConnection }) => async function updateDashboard(params) {
|
|
27
|
-
const { dashboardId, code, title, description, roles, evaluate } = new UpdateDashboardParams(params);
|
|
29
|
+
const { dashboardId, code, title, description, isPinned, roles, evaluate } = new UpdateDashboardParams(params);
|
|
28
30
|
|
|
29
31
|
const Dashboard = studioConnection.models['__Studio_Dashboard'];
|
|
30
32
|
|
|
31
33
|
await authorize('Dashboard.updateDashboard', roles);
|
|
32
34
|
|
|
33
|
-
const updateObj = {
|
|
35
|
+
const updateObj = {};
|
|
36
|
+
|
|
37
|
+
if (code != null) {
|
|
38
|
+
updateObj.code = code;
|
|
39
|
+
}
|
|
34
40
|
|
|
35
41
|
if (title != null) {
|
|
36
42
|
updateObj.title = title;
|
|
@@ -40,8 +46,15 @@ module.exports = ({ studioConnection }) => async function updateDashboard(params
|
|
|
40
46
|
updateObj.description = description;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
if (isPinned != null) {
|
|
50
|
+
updateObj.isPinned = isPinned;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const doc = await Dashboard.findByIdAndUpdate(
|
|
54
|
+
dashboardId,
|
|
55
|
+
updateObj,
|
|
56
|
+
{ sanitizeFilter: true, returnDocument: 'after' }
|
|
57
|
+
);
|
|
45
58
|
|
|
46
59
|
return { doc };
|
|
47
60
|
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
const { Buffer } = require('buffer');
|
|
6
|
+
const validateDocumentWithTimeout = require('../../helpers/validateDocumentWithTimeout');
|
|
7
|
+
|
|
8
|
+
const SAMPLE_SIZE = 1000;
|
|
9
|
+
const MAX_SCHEMA_PATH_DEPTH = 4;
|
|
10
|
+
|
|
11
|
+
const AnalyzeSchemaParams = new Archetype({
|
|
12
|
+
model: {
|
|
13
|
+
$type: 'string',
|
|
14
|
+
$required: true
|
|
15
|
+
},
|
|
16
|
+
roles: {
|
|
17
|
+
$type: ['string']
|
|
18
|
+
}
|
|
19
|
+
}).compile('AnalyzeSchemaParams');
|
|
20
|
+
|
|
21
|
+
module.exports = ({ db }) => async function analyzeSchema(params) {
|
|
22
|
+
const { model, roles } = new AnalyzeSchemaParams(params);
|
|
23
|
+
|
|
24
|
+
await authorize('Model.analyzeSchema', roles);
|
|
25
|
+
|
|
26
|
+
const Model = db.models[model];
|
|
27
|
+
if (Model == null) {
|
|
28
|
+
throw new Error(`Model ${model} not found`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const documentCount = await Model.collection.countDocuments();
|
|
32
|
+
const rawDocs = documentCount > SAMPLE_SIZE ?
|
|
33
|
+
await Model.collection.aggregate([{ $sample: { size: SAMPLE_SIZE } }]).toArray() :
|
|
34
|
+
await Model.collection.find({}).toArray();
|
|
35
|
+
|
|
36
|
+
const paths = getSchemaPaths(Model.schema);
|
|
37
|
+
const pathTypeCounts = {};
|
|
38
|
+
const pathValueCounts = {};
|
|
39
|
+
|
|
40
|
+
for (const path of paths) {
|
|
41
|
+
pathTypeCounts[path.path] = {};
|
|
42
|
+
pathValueCounts[path.path] = 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const validationResults = await Promise.all(rawDocs.map(rawDoc => validateDocumentWithTimeout(Model.hydrate(rawDoc))));
|
|
46
|
+
const validDocumentCount = validationResults.filter(result => result.valid).length;
|
|
47
|
+
const firstValidDocument = validationResults.find(result => result.valid);
|
|
48
|
+
const firstInvalidDocument = validationResults.find(result => !result.valid);
|
|
49
|
+
|
|
50
|
+
for (const rawDoc of rawDocs) {
|
|
51
|
+
for (const path of paths) {
|
|
52
|
+
for (const value of getPathValues(rawDoc, path.path, path.arrayParents)) {
|
|
53
|
+
const types = path.arrayPath ? [getType(value)] : getTypes(value);
|
|
54
|
+
pathValueCounts[path.path] += types.length;
|
|
55
|
+
for (const type of types) {
|
|
56
|
+
pathTypeCounts[path.path][type] = (pathTypeCounts[path.path][type] || 0) + 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
analysis: {
|
|
64
|
+
documentCount,
|
|
65
|
+
sampleSize: rawDocs.length,
|
|
66
|
+
sampled: documentCount > SAMPLE_SIZE,
|
|
67
|
+
validDocumentCount,
|
|
68
|
+
invalidDocumentCount: rawDocs.length - validDocumentCount,
|
|
69
|
+
firstValidDocumentId: firstValidDocument?.documentId || null,
|
|
70
|
+
firstInvalidDocumentId: firstInvalidDocument?.documentId || null,
|
|
71
|
+
invalidDocuments: validationResults.filter(result => !result.valid).map(result => ({
|
|
72
|
+
documentId: result.documentId,
|
|
73
|
+
error: result.error,
|
|
74
|
+
errors: result.errors
|
|
75
|
+
})),
|
|
76
|
+
paths: paths.map(path => ({
|
|
77
|
+
path: path.path,
|
|
78
|
+
valueCount: pathValueCounts[path.path],
|
|
79
|
+
...(path.required ? { required: true } : {}),
|
|
80
|
+
types: Object.keys(pathTypeCounts[path.path]).map(type => ({
|
|
81
|
+
type,
|
|
82
|
+
count: pathTypeCounts[path.path][type]
|
|
83
|
+
})).sort((type1, type2) => type2.count - type1.count)
|
|
84
|
+
}))
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function getSchemaPaths(schema, prefix, parentArrayParents) {
|
|
90
|
+
prefix = prefix || '';
|
|
91
|
+
parentArrayParents = parentArrayParents || [];
|
|
92
|
+
|
|
93
|
+
const paths = [];
|
|
94
|
+
for (const path of Object.keys(schema.paths)) {
|
|
95
|
+
const fullPath = prefix ? `${prefix}.${path}` : path;
|
|
96
|
+
const pathDepth = fullPath.split('.').length;
|
|
97
|
+
if (pathDepth > MAX_SCHEMA_PATH_DEPTH) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const schemaType = schema.paths[path];
|
|
101
|
+
const arrayParents = parentArrayParents.concat(getSchemaArrayParents(schemaType, fullPath));
|
|
102
|
+
paths.push({
|
|
103
|
+
path: fullPath,
|
|
104
|
+
arrayParents,
|
|
105
|
+
arrayPath: isSchemaArrayPath(schemaType),
|
|
106
|
+
required: schemaType.options?.required === true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (schemaType.schema && pathDepth < MAX_SCHEMA_PATH_DEPTH) {
|
|
110
|
+
paths.push(...getSchemaPaths(schemaType.schema, fullPath, arrayParents));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return paths;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getSchemaArrayParents(schemaType, fullPath) {
|
|
117
|
+
if (isSchemaArrayPath(schemaType)) {
|
|
118
|
+
return [fullPath];
|
|
119
|
+
}
|
|
120
|
+
const parent = schemaType.$parentSchemaDocArray;
|
|
121
|
+
if (parent?.path) {
|
|
122
|
+
return [parent.path];
|
|
123
|
+
}
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function isSchemaArrayPath(schemaType) {
|
|
128
|
+
return schemaType.$isMongooseDocumentArray || schemaType.$isMongooseArray || schemaType.instance === 'Array';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getPathValues(doc, path, arrayParents) {
|
|
132
|
+
return getPathValuesRecursive(doc, path.split('.'), '', new Set(arrayParents));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getPathValuesRecursive(value, parts, currentPath, arrayParents) {
|
|
136
|
+
if (parts.length === 0) {
|
|
137
|
+
return [value];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value.flatMap(element => getPathValuesRecursive(element, parts, currentPath, arrayParents));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (value == null) {
|
|
145
|
+
return [undefined];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const nextPart = parts[0];
|
|
149
|
+
const nextPath = currentPath ? `${currentPath}.${nextPart}` : nextPart;
|
|
150
|
+
const nextValue = getPathPartValue(value, nextPart);
|
|
151
|
+
if (nextValue == null && arrayParents.has(nextPath)) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return getPathValuesRecursive(nextValue, parts.slice(1), nextPath, arrayParents);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getPathPartValue(value, pathPart) {
|
|
159
|
+
if (value instanceof Map) {
|
|
160
|
+
return value.get(pathPart);
|
|
161
|
+
}
|
|
162
|
+
return value[pathPart];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getTypes(value) {
|
|
166
|
+
if (!Array.isArray(value)) {
|
|
167
|
+
return [getType(value)];
|
|
168
|
+
}
|
|
169
|
+
if (value.length === 0) {
|
|
170
|
+
return ['array'];
|
|
171
|
+
}
|
|
172
|
+
const types = new Set();
|
|
173
|
+
for (const element of value) {
|
|
174
|
+
types.add(getType(element));
|
|
175
|
+
}
|
|
176
|
+
return Array.from(types);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getType(value) {
|
|
180
|
+
if (value === undefined) {
|
|
181
|
+
return 'undefined';
|
|
182
|
+
}
|
|
183
|
+
if (value === null) {
|
|
184
|
+
return 'null';
|
|
185
|
+
}
|
|
186
|
+
if (Array.isArray(value)) {
|
|
187
|
+
return 'array';
|
|
188
|
+
}
|
|
189
|
+
if (value instanceof Date) {
|
|
190
|
+
return 'date';
|
|
191
|
+
}
|
|
192
|
+
if (Buffer.isBuffer(value)) {
|
|
193
|
+
return 'buffer';
|
|
194
|
+
}
|
|
195
|
+
if (value?._bsontype === 'ObjectId') {
|
|
196
|
+
return 'objectId';
|
|
197
|
+
}
|
|
198
|
+
if (value?._bsontype === 'Decimal128') {
|
|
199
|
+
return 'decimal128';
|
|
200
|
+
}
|
|
201
|
+
return typeof value;
|
|
202
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
exports.addField = require('./addField');
|
|
4
|
+
exports.analyzeSchema = require('./analyzeSchema');
|
|
4
5
|
exports.createChatMessage = require('./createChatMessage');
|
|
5
6
|
exports.createDocument = require('./createDocument');
|
|
6
7
|
exports.deleteDocument = require('./deleteDocument');
|
|
@@ -21,3 +22,4 @@ exports.streamDocumentChanges = require('./streamDocumentChanges');
|
|
|
21
22
|
exports.streamChatMessage = require('./streamChatMessage');
|
|
22
23
|
exports.updateDocument = require('./updateDocument');
|
|
23
24
|
exports.updateDocuments = require('./updateDocuments');
|
|
25
|
+
exports.validateDocument = require('./validateDocument');
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
const validateDocumentWithTimeout = require('../../helpers/validateDocumentWithTimeout');
|
|
6
|
+
|
|
7
|
+
const ValidateDocumentParams = new Archetype({
|
|
8
|
+
model: {
|
|
9
|
+
$type: 'string',
|
|
10
|
+
$required: true
|
|
11
|
+
},
|
|
12
|
+
documentId: {
|
|
13
|
+
$type: 'string',
|
|
14
|
+
$required: true
|
|
15
|
+
},
|
|
16
|
+
roles: {
|
|
17
|
+
$type: ['string']
|
|
18
|
+
}
|
|
19
|
+
}).compile('ValidateDocumentParams');
|
|
20
|
+
|
|
21
|
+
module.exports = ({ db }) => async function validateDocument(params) {
|
|
22
|
+
const { model, documentId, roles } = new ValidateDocumentParams(params);
|
|
23
|
+
|
|
24
|
+
await authorize('Model.validateDocument', roles);
|
|
25
|
+
|
|
26
|
+
const Model = db.models[model];
|
|
27
|
+
if (Model == null) {
|
|
28
|
+
throw new Error(`Model ${model} not found`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const doc = await Model.findById(documentId).setOptions({ sanitizeFilter: true }).orFail();
|
|
32
|
+
return {
|
|
33
|
+
result: await validateDocumentWithTimeout(doc)
|
|
34
|
+
};
|
|
35
|
+
};
|
package/backend/authorize.js
CHANGED
|
@@ -14,7 +14,9 @@ const actionsToRequiredRoles = {
|
|
|
14
14
|
'Dashboard.getDashboards': ['owner', 'admin', 'member', 'readonly', 'dashboards'],
|
|
15
15
|
'Dashboard.updateDashboard': ['owner', 'admin', 'member'],
|
|
16
16
|
'Model.createDocument': ['owner', 'admin', 'member'],
|
|
17
|
+
'Model.analyzeSchema': ['owner', 'admin', 'member', 'readonly'],
|
|
17
18
|
'Model.updateDocument': ['owner', 'admin', 'member'],
|
|
19
|
+
'Model.validateDocument': ['owner', 'admin', 'member', 'readonly'],
|
|
18
20
|
'Model.deleteDocument': ['owner', 'admin', 'member'],
|
|
19
21
|
'Model.deleteDocuments': ['owner', 'admin', 'member'],
|
|
20
22
|
'Model.dropCollection': ['owner', 'admin'],
|
|
@@ -13,6 +13,37 @@ const dashboardSchema = new mongoose.Schema({
|
|
|
13
13
|
},
|
|
14
14
|
description: {
|
|
15
15
|
type: String
|
|
16
|
+
},
|
|
17
|
+
isPinned: {
|
|
18
|
+
type: Boolean
|
|
19
|
+
},
|
|
20
|
+
createdById: {
|
|
21
|
+
type: mongoose.Schema.Types.ObjectId
|
|
22
|
+
},
|
|
23
|
+
createdBy: {
|
|
24
|
+
name: String,
|
|
25
|
+
email: String
|
|
26
|
+
},
|
|
27
|
+
lastEvaluatedAt: {
|
|
28
|
+
type: Date
|
|
29
|
+
}
|
|
30
|
+
}, { timestamps: true });
|
|
31
|
+
|
|
32
|
+
dashboardSchema.post(['find', 'findOne'], async function(docs) {
|
|
33
|
+
const dashboards = Array.isArray(docs) ? docs : [docs];
|
|
34
|
+
for (const dashboard of dashboards) {
|
|
35
|
+
if (dashboard != null && dashboard.createdAt == null && dashboard._id?.getTimestamp) {
|
|
36
|
+
const createdAt = dashboard._id.getTimestamp();
|
|
37
|
+
if (!createdAt.valueOf()) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
dashboard.set('createdAt', createdAt, { overwriteImmutable: true });
|
|
41
|
+
await this.model.db.model('__Studio_Dashboard').updateOne(
|
|
42
|
+
{ _id: dashboard._id },
|
|
43
|
+
{ createdAt },
|
|
44
|
+
{ overwriteImmutable: true }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
16
47
|
}
|
|
17
48
|
});
|
|
18
49
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const VALIDATION_TIMEOUT_MS = 1000;
|
|
4
|
+
|
|
5
|
+
module.exports = async function validateDocumentWithTimeout(doc) {
|
|
6
|
+
const documentId = doc._id?.toString() || null;
|
|
7
|
+
try {
|
|
8
|
+
await Promise.race([
|
|
9
|
+
doc.validate(),
|
|
10
|
+
new Promise((resolve, reject) => setTimeout(() => reject(new Error('Validation timed out')), VALIDATION_TIMEOUT_MS))
|
|
11
|
+
]);
|
|
12
|
+
return { valid: true, documentId, error: null, errors: null };
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return {
|
|
15
|
+
valid: false,
|
|
16
|
+
documentId,
|
|
17
|
+
error: err?.message || String(err),
|
|
18
|
+
errors: formatValidationErrors(err)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function formatValidationErrors(err) {
|
|
24
|
+
if (err?.name !== 'ValidationError' || err.errors == null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const errors = {};
|
|
29
|
+
for (const [path, pathError] of Object.entries(err.errors)) {
|
|
30
|
+
errors[path] = {
|
|
31
|
+
message: pathError?.message || String(pathError),
|
|
32
|
+
name: pathError?.name || null,
|
|
33
|
+
kind: pathError?.kind || null,
|
|
34
|
+
path: pathError?.path || path,
|
|
35
|
+
value: pathError?.value
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
package/backend/netlify.js
CHANGED
package/backend/next.js
CHANGED
package/express.js
CHANGED
|
@@ -87,6 +87,7 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
87
87
|
req._internals.initiatedById = user._id;
|
|
88
88
|
req._internals.roles = roles;
|
|
89
89
|
req._internals.$workspaceId = workspace._id;
|
|
90
|
+
req._internals.initiatedBy = user;
|
|
90
91
|
|
|
91
92
|
next();
|
|
92
93
|
})
|