@mongoosejs/studio 0.0.83 → 0.0.84

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/astra.js ADDED
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ const mongoose = require('mongoose');
4
+ const { createAstraUri, driver, Vectorize } = require('@datastax/astra-mongoose');
5
+ const express = require('express');
6
+ const studio = require('./express');
7
+
8
+ const app = express();
9
+
10
+ const m = new mongoose.Mongoose();
11
+ m.setDriver(driver);
12
+ m.set('autoCreate', false);
13
+ m.set('autoIndex', false);
14
+
15
+ (function () {
16
+ const schema = new m.Schema({
17
+ email: {
18
+ type: String,
19
+ required: true,
20
+ unique: true
21
+ },
22
+ firstName: {
23
+ type: String,
24
+ required: true
25
+ },
26
+ lastName: {
27
+ type: String,
28
+ required: true
29
+ }
30
+ });
31
+
32
+ schema.virtual('displayName').get(function() {
33
+ return this.firstName + ' ' + this.lastName.slice(0, 1) + '.';
34
+ });
35
+
36
+ m.model('User', schema);
37
+ })();
38
+
39
+ (function () {
40
+ const schema = new m.Schema({
41
+ make: {
42
+ type: String,
43
+ required: true
44
+ },
45
+ model: {
46
+ type: String,
47
+ required: true
48
+ },
49
+ year: {
50
+ type: Number,
51
+ required: true,
52
+ validate: (v) => Number.isInteger(v) && v >= 1950
53
+ },
54
+ images: {
55
+ type: [String]
56
+ },
57
+ numReviews: {
58
+ type: Number,
59
+ required: true,
60
+ default: 0
61
+ },
62
+ averageReview: {
63
+ type: Number,
64
+ required: true,
65
+ default: 0
66
+ }
67
+ });
68
+
69
+ m.model('Vehicle', schema);
70
+ })();
71
+
72
+ (function () {
73
+ const schema = new m.Schema({
74
+ rating: {
75
+ type: Number,
76
+ required: true,
77
+ validate: (v) => Number.isInteger(v)
78
+ },
79
+ text: {
80
+ type: String,
81
+ required: true,
82
+ validate: (v) => v.length >= 30
83
+ },
84
+ userId: {
85
+ type: 'ObjectId',
86
+ required: true
87
+ },
88
+ vehicleId: {
89
+ type: 'ObjectId',
90
+ required: true
91
+ }
92
+ }, { timestamps: true });
93
+
94
+ schema.virtual('user', {
95
+ ref: 'User',
96
+ localField: 'userId',
97
+ foreignField: '_id',
98
+ justOne: true
99
+ });
100
+
101
+ schema.virtual('vehicle', {
102
+ ref: 'Vehicle',
103
+ localField: 'vehicleId',
104
+ foreignField: '_id',
105
+ justOne: true
106
+ });
107
+
108
+ schema.pre('save', async function updateVehicleRating() {
109
+ if (!this.isNew) {
110
+ return;
111
+ }
112
+ const vehicle = await mongoose.model('Vehicle').findById(this.vehicleId).orFail();
113
+ vehicle.numReviews += 1;
114
+ const vehicleReviews = await mongoose.model('Review').find({ vehicleId: this.vehicleId });
115
+ const reviewRatings = vehicleReviews.map((entry) => entry.rating);
116
+ reviewRatings.push(this.rating);
117
+ const average = calculateAverage(reviewRatings);
118
+ vehicle.averageReview = average;
119
+ await vehicle.save();
120
+ });
121
+
122
+ function calculateAverage(ratings) {
123
+ if (ratings.length === 0) {
124
+ return 0;
125
+ }
126
+ let sum = 0;
127
+ for (let i = 0; i < ratings.length; i++) {
128
+ sum += ratings[i];
129
+ }
130
+ const average = sum / ratings.length;
131
+ return average;
132
+ }
133
+
134
+ m.model('Review', schema);
135
+ })();
136
+
137
+ const uri = createAstraUri(
138
+ 'https://fbced748-d6ed-40b1-ab9d-b618015a2c2c-us-east-2.apps.astra.datastax.com',
139
+ 'AstraCS:kjSUArrrkLKrDlrMpUQOUYlw:50c9eda620df1099ad2dd77eb36e7755daaa48494acb158ff6de8df3deee40f6',
140
+ 'reviews_dev2'
141
+ );
142
+
143
+ void async function main() {
144
+ await m.connect(uri);
145
+ const studioConnection = await mongoose.createConnection('mongodb://127.0.0.1:27017/mongoose_studio_test').asPromise();
146
+
147
+ app.use('/studio', await studio('/studio/api', m, { __build: true, apiKey: '123456789', studioConnection }));
148
+
149
+ await app.listen(7770);
150
+ console.log('Listening on port 7770');
151
+ }();
152
+
153
+ /*
154
+ # Astra Notes
155
+
156
+ 1. Must use collections. Tables don't support `countDocuments()` or `estimatedDocumentCount()`.
157
+ 2. Annoying issue: collections don't let you store keys that start with '$'
158
+
159
+ */
@@ -16,9 +16,9 @@ const ExecuteScriptParams = new Archetype({
16
16
  }
17
17
  }).compile('ExecuteScriptParams');
18
18
 
19
- module.exports = ({ db }) => async function executeScript(params) {
19
+ module.exports = ({ db, studioConnection }) => async function executeScript(params) {
20
20
  const { userId, chatMessageId, script } = new ExecuteScriptParams(params);
21
- const ChatMessage = db.model('__Studio_ChatMessage');
21
+ const ChatMessage = studioConnection.model('__Studio_ChatMessage');
22
22
 
23
23
  const chatMessage = await ChatMessage.findById(chatMessageId);
24
24
  if (!chatMessage) {
@@ -55,10 +55,10 @@ return { numUsers: users.length };
55
55
  Here is a description of the user's models. Assume these are the only models available in the system unless explicitly instructed otherwise by the user.
56
56
  `.trim();
57
57
 
58
- module.exports = ({ db }) => async function createChatMessage(params) {
58
+ module.exports = ({ db, studioConnection }) => async function createChatMessage(params) {
59
59
  const { chatThreadId, userId, content, script, authorization } = new CreateChatMessageParams(params);
60
- const ChatThread = db.model('__Studio_ChatThread');
61
- const ChatMessage = db.model('__Studio_ChatMessage');
60
+ const ChatThread = studioConnection.model('__Studio_ChatThread');
61
+ const ChatMessage = studioConnection.model('__Studio_ChatMessage');
62
62
 
63
63
  // Check that the user owns the thread
64
64
  const chatThread = await ChatThread.findOne({ _id: chatThreadId });
@@ -9,9 +9,9 @@ const CreateChatThreadParams = new Archetype({
9
9
  }
10
10
  }).compile('CreateChatThreadParams');
11
11
 
12
- module.exports = ({ db }) => async function createChatThread(params) {
12
+ module.exports = ({ studioConnection }) => async function createChatThread(params) {
13
13
  const { userId } = new CreateChatThreadParams(params);
14
- const ChatThread = db.model('__Studio_ChatThread');
14
+ const ChatThread = studioConnection.model('__Studio_ChatThread');
15
15
 
16
16
  const chatThread = await ChatThread.create({ userId });
17
17
 
@@ -12,10 +12,10 @@ const GetChatThreadParams = new Archetype({
12
12
  }
13
13
  }).compile('GetChatThreadParams');
14
14
 
15
- module.exports = ({ db }) => async function getChatThread(params) {
15
+ module.exports = ({ db, studioConnection }) => async function getChatThread(params) {
16
16
  const { chatThreadId, userId } = new GetChatThreadParams(params);
17
- const ChatThread = db.model('__Studio_ChatThread');
18
- const ChatMessage = db.model('__Studio_ChatMessage');
17
+ const ChatThread = studioConnection.model('__Studio_ChatThread');
18
+ const ChatMessage = studioConnection.model('__Studio_ChatMessage');
19
19
 
20
20
  const chatThread = await ChatThread.findById(chatThreadId);
21
21
 
@@ -9,10 +9,10 @@ const ListChatThreadsParams = new Archetype({
9
9
  }
10
10
  }).compile('ListChatThreadsParams');
11
11
 
12
- module.exports = ({ db }) => async function listChatThreads(params) {
12
+ module.exports = ({ db, studioConnection }) => async function listChatThreads(params) {
13
13
  // Just validate the params object, but no actual parameters needed
14
14
  const { userId } = new ListChatThreadsParams(params);
15
- const ChatThread = db.model('__Studio_ChatThread');
15
+ const ChatThread = studioConnection.model('__Studio_ChatThread');
16
16
 
17
17
  // Get all chat threads
18
18
  const chatThreads = await ChatThread.find(userId ? { userId } : {})
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+
5
+ const DeleteDocumentsParams = new Archetype({
6
+ model: {
7
+ $type: 'string',
8
+ $required: true
9
+ },
10
+ documentIds: {
11
+ $type: 'string',
12
+ $required: true
13
+ },
14
+ roles: {
15
+ $type: ['string'],
16
+ }
17
+ }).compile('DeleteDocumentsParams');
18
+
19
+ module.exports = ({ db }) => async function DeleteDocuments(params) {
20
+ const { model, documentIds, roles } = new DeleteDocumentsParams(params);
21
+
22
+ const Model = db.models[model];
23
+
24
+ if (roles && roles.includes('readonly')) {
25
+ throw new Error('Not authorized');
26
+ }
27
+ if (Model == null) {
28
+ throw new Error(`Model ${model} not found`);
29
+ }
30
+
31
+ await Model.
32
+ deleteMany({_id: { $in: documentIds }}).
33
+ setOptions({ sanitizeFilter: true }).
34
+ orFail();
35
+
36
+
37
+ return { };
38
+ };
@@ -65,10 +65,10 @@ module.exports = ({ db }) => async function getDocuments(params) {
65
65
  const numDocuments = filter == null ?
66
66
  await Model.estimatedDocumentCount() :
67
67
  await Model.countDocuments(filter);
68
-
68
+
69
69
  return {
70
70
  docs: docs.map(doc => doc.toJSON({ virtuals: false, getters: false, transform: false })),
71
71
  schemaPaths,
72
72
  numDocs: numDocuments
73
73
  };
74
- };
74
+ };
@@ -2,9 +2,11 @@
2
2
 
3
3
  exports.createDocument = require('./createDocument')
4
4
  exports.deleteDocument = require('./deleteDocument');
5
+ exports.deleteDocuments = require('./deleteDocuments');
5
6
  exports.exportQueryResults = require('./exportQueryResults');
6
7
  exports.getDocument = require('./getDocument');
7
8
  exports.getDocuments = require('./getDocuments');
8
9
  exports.getIndexes = require('./getIndexes');
9
10
  exports.listModels = require('./listModels');
10
11
  exports.updateDocument = require('./updateDocument');
12
+ exports.updateDocuments = require('./updateDocuments');
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const mongoose = require('mongoose');
5
+
6
+ const UpdateDocumentsParams = new Archetype({
7
+ model: {
8
+ $type: 'string',
9
+ $required: true
10
+ },
11
+ _id: {
12
+ $type: ['string'],
13
+ $required: true
14
+ },
15
+ update: {
16
+ $type: Object,
17
+ $required: true
18
+ },
19
+ roles: {
20
+ $type: ['string'],
21
+ }
22
+ }).compile('UpdateDocumentsParams');
23
+
24
+ module.exports = ({ db }) => async function updateDocuments(params) {
25
+ const { model, _id, update, roles } = new UpdateDocumentsParams(params);
26
+
27
+ if (roles && roles.includes('readonly')) {
28
+ throw new Error('Not authorized');
29
+ }
30
+ const Model = db.models[model];
31
+ if (Model == null) {
32
+ throw new Error(`Model ${model} not found`);
33
+ }
34
+
35
+ let processedUpdate = update;
36
+ if (Object.keys(update).length > 0) {
37
+ processedUpdate = Object.fromEntries(
38
+ Object.entries(update).map(([key, value]) => [key, value === 'null' ? null : value === 'undefined' ? undefined : value])
39
+ );
40
+ }
41
+
42
+ const result = await Model.
43
+ updateMany({ _id: { $in: _id } }, processedUpdate, { overwriteImmutable: true, runValidators: false });
44
+
45
+ return { result };
46
+ };
package/backend/index.js CHANGED
@@ -8,15 +8,14 @@ const chatMessageSchema = require('./db/chatMessageSchema');
8
8
  const chatThreadSchema = require('./db/chatThreadSchema');
9
9
  const dashboardSchema = require('./db/dashboardSchema');
10
10
 
11
- const getModelDescriptions = require('./helpers/getModelDescriptions');
12
-
13
- module.exports = function backend(db) {
11
+ module.exports = function backend(db, studioConnection) {
14
12
  db = db || mongoose.connection;
15
13
 
16
- const Dashboard = db.model('__Studio_Dashboard', dashboardSchema, 'studio__dashboards');
17
- const ChatMessage = db.model('__Studio_ChatMessage', chatMessageSchema, 'studio__chatMessages');
18
- const ChatThread = db.model('__Studio_ChatThread', chatThreadSchema, 'studio__chatThreads');
14
+ studioConnection = studioConnection ?? db;
15
+ const Dashboard = studioConnection.model('__Studio_Dashboard', dashboardSchema, 'studio__dashboards');
16
+ const ChatMessage = studioConnection.model('__Studio_ChatMessage', chatMessageSchema, 'studio__chatMessages');
17
+ const ChatThread = studioConnection.model('__Studio_ChatThread', chatThreadSchema, 'studio__chatThreads');
19
18
 
20
- const actions = applySpec(Actions, { db });
19
+ const actions = applySpec(Actions, { db, studioConnection });
21
20
  return actions;
22
21
  };
package/express.js CHANGED
@@ -31,7 +31,7 @@ module.exports = async function(apiUrl, conn, options) {
31
31
  }
32
32
 
33
33
  apiUrl = apiUrl || 'api';
34
- const backend = Backend(conn);
34
+ const backend = Backend(conn, options?.studioConnection);
35
35
 
36
36
  router.use(
37
37
  '/api',