@mongoosejs/studio 0.0.130 → 0.0.133

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.
@@ -1399,6 +1399,11 @@ video {
1399
1399
  border-style: none;
1400
1400
  }
1401
1401
 
1402
+ .border-amber-400 {
1403
+ --tw-border-opacity: 1;
1404
+ border-color: rgb(251 191 36 / var(--tw-border-opacity));
1405
+ }
1406
+
1402
1407
  .border-blue-300 {
1403
1408
  --tw-border-opacity: 1;
1404
1409
  border-color: rgb(147 197 253 / var(--tw-border-opacity));
@@ -1433,6 +1438,11 @@ video {
1433
1438
  border-color: rgb(63 83 255 / var(--tw-border-opacity));
1434
1439
  }
1435
1440
 
1441
+ .bg-amber-100 {
1442
+ --tw-bg-opacity: 1;
1443
+ background-color: rgb(254 243 199 / var(--tw-bg-opacity));
1444
+ }
1445
+
1436
1446
  .bg-black {
1437
1447
  --tw-bg-opacity: 1;
1438
1448
  background-color: rgb(0 0 0 / var(--tw-bg-opacity));
@@ -2044,6 +2054,11 @@ video {
2044
2054
  --tw-ring-inset: inset;
2045
2055
  }
2046
2056
 
2057
+ .ring-amber-200 {
2058
+ --tw-ring-opacity: 1;
2059
+ --tw-ring-color: rgb(253 230 138 / var(--tw-ring-opacity));
2060
+ }
2061
+
2047
2062
  .ring-black {
2048
2063
  --tw-ring-opacity: 1;
2049
2064
  --tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity));
@@ -2074,6 +2089,10 @@ video {
2074
2089
  --tw-ring-opacity: 0.05;
2075
2090
  }
2076
2091
 
2092
+ .ring-offset-1 {
2093
+ --tw-ring-offset-width: 1px;
2094
+ }
2095
+
2077
2096
  .filter {
2078
2097
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
2079
2098
  }
@@ -2141,6 +2160,16 @@ video {
2141
2160
  --tw-ring-color: rgb(0 168 165 / var(--tw-ring-opacity));
2142
2161
  }
2143
2162
 
2163
+ .hover\:translate-x-0:hover {
2164
+ --tw-translate-x: 0px;
2165
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
2166
+ }
2167
+
2168
+ .hover\:translate-x-0\.5:hover {
2169
+ --tw-translate-x: 0.125rem;
2170
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
2171
+ }
2172
+
2144
2173
  .hover\:border-gray-300:hover {
2145
2174
  --tw-border-opacity: 1;
2146
2175
  border-color: rgb(209 213 219 / var(--tw-border-opacity));
@@ -1,3 +1,3 @@
1
- <div>
2
- {{value}}
1
+ <div class="w-full">
2
+ <pre class="w-full whitespace-pre-wrap break-words font-mono text-sm text-gray-700 m-0">{{displayValue}}</pre>
3
3
  </div>
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const template = require('./detail-default.html');
4
-
5
4
  module.exports = app => app.component('detail-default', {
6
5
  template: template,
7
6
  props: ['value'],
@@ -13,7 +12,17 @@ module.exports = app => app.component('detail-default', {
13
12
  if (this.value === undefined) {
14
13
  return 'undefined';
15
14
  }
16
- return this.value;
15
+ if (typeof this.value === 'string') {
16
+ return this.value;
17
+ }
18
+ if (typeof this.value === 'number' || typeof this.value === 'boolean' || typeof this.value === 'bigint') {
19
+ return String(this.value);
20
+ }
21
+ try {
22
+ return JSON.stringify(this.value, null, 2);
23
+ } catch (err) {
24
+ return String(this.value);
25
+ }
17
26
  }
18
27
  }
19
28
  });
@@ -1,18 +1,18 @@
1
1
  <div>
2
- <h2>
3
- Are you sure you want to delete the following document?
4
- </h2>
5
- <pre class="max-h-[50vh] overflow-auto"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
6
- <div class="flex gap-2 mt-2">
7
- <async-button
8
- class="rounded-md bg-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
9
- @click="startDelete">
10
- Confirm
11
- </async-button>
12
- <button
13
- class="rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
14
- @click="closeDelete">
15
- Cancel
16
- </button>
17
- </div>
18
- </div>
2
+ <h2>
3
+ Are you sure you want to delete the following document?
4
+ </h2>
5
+ <pre class="max-h-[50vh] overflow-auto"><code ref="code" class="language-javascript" v-text="displayValue"></code></pre>
6
+ <div class="flex gap-2 mt-2">
7
+ <async-button
8
+ class="rounded-md bg-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600"
9
+ @click="startDelete">
10
+ Confirm
11
+ </async-button>
12
+ <button
13
+ class="rounded-md bg-gray-200 px-2.5 py-1.5 text-sm font-semibold text-black shadow-sm hover:bg-gray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-300"
14
+ @click="closeDelete">
15
+ Cancel
16
+ </button>
17
+ </div>
18
+ </div>
@@ -21,10 +21,10 @@
21
21
  </div>
22
22
 
23
23
  <!-- Data Type Filter -->
24
- <div class="relative">
24
+ <div class="relative hidden md:block">
25
25
  <select
26
26
  v-model="selectedType"
27
- class="hidden md:block px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none"
27
+ class="px-4 py-2 pr-8 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white min-w-[140px] appearance-none"
28
28
  >
29
29
  <option value="">All Types</option>
30
30
  <option v-for="type in availableTypes" :key="type" :value="type">
@@ -41,12 +41,11 @@
41
41
  <!-- Add Field Button -->
42
42
  <button
43
43
  @click="openAddFieldModal"
44
- class="hidden md:block px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 flex items-center gap-2"
44
+ class="hidden md:flex px-4 py-2 text-sm font-medium text-white bg-green-600 hover:bg-green-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 items-center gap-2"
45
45
  >
46
46
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
47
47
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
48
- </svg>
49
- Add Field
48
+ </svg> Add Field
50
49
  </button>
51
50
  </div>
52
51
  </div>
@@ -54,18 +53,32 @@
54
53
  <!-- Fields View -->
55
54
  <div v-if="viewMode === 'fields'">
56
55
  <!-- Schema Paths -->
57
- <div v-for="path in filteredSchemaPaths" :key="path" class="value">
56
+ <div
57
+ v-for="path in filteredSchemaPaths"
58
+ :key="path.path || path"
59
+ class="value"
60
+ >
58
61
  <document-property
59
62
  :path="path"
60
63
  :document="document"
61
64
  :schemaPaths="schemaPaths"
62
65
  :editting="editting"
63
66
  :changes="changes"
67
+ :highlight="isSchemaPathMatched(path)"
64
68
  :invalid="invalid"></document-property>
65
69
  </div>
66
70
 
67
71
  <!-- Virtual Fields -->
68
- <div v-for="path in filteredVirtuals" class="border border-gray-200 rounded-lg mb-2">
72
+ <div
73
+ v-for="path in filteredVirtuals"
74
+ :key="path.name"
75
+ class="border rounded-lg mb-2 transition-all duration-200 ease-in-out"
76
+ :class="[
77
+ isVirtualMatched(path)
78
+ ? 'border-amber-400 ring-2 ring-amber-200 ring-offset-1'
79
+ : 'border-gray-200'
80
+ ]"
81
+ >
69
82
  <!-- Virtual Field Header (Always Visible) -->
70
83
  <div
71
84
  @click="toggleVirtualField(path.name)"
@@ -99,7 +112,7 @@
99
112
  </div>
100
113
 
101
114
  <!-- No Results Message -->
102
- <div v-if="searchQuery && filteredSchemaPaths.length === 0 && filteredVirtuals.length === 0" class="text-center py-8 text-gray-500">
115
+ <div v-if="searchQuery.trim() && !hasSearchMatches" class="text-center py-8 text-gray-500">
103
116
  <svg class="w-12 h-12 mx-auto mb-4 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
104
117
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.709M15 6.291A7.962 7.962 0 0012 5c-2.34 0-4.29 1.009-5.824 2.709"></path>
105
118
  </svg>
@@ -115,38 +115,39 @@ module.exports = app => app.component('document-details', {
115
115
  shouldUseDatePicker() {
116
116
  return this.fieldData.type === 'Date';
117
117
  },
118
- filteredSchemaPaths() {
118
+ typeFilteredSchemaPaths() {
119
119
  let paths = this.schemaPaths || [];
120
120
 
121
- // Filter by search query
122
- if (this.searchQuery.trim()) {
123
- const query = this.searchQuery.toLowerCase();
124
- paths = paths.filter(path =>
125
- path.path.toLowerCase().includes(query)
126
- );
127
- }
128
-
129
- // Filter by data type
130
121
  if (this.selectedType) {
131
- paths = paths.filter(path =>
132
- path.instance === this.selectedType
133
- );
122
+ paths = paths.filter(path => path.instance === this.selectedType);
134
123
  }
135
124
 
136
125
  return paths;
137
126
  },
138
- filteredVirtuals() {
139
- let virtuals = this.virtuals;
127
+ filteredSchemaPaths() {
128
+ const paths = this.typeFilteredSchemaPaths.slice();
140
129
 
141
- // Filter by search query
142
- if (this.searchQuery.trim()) {
143
- const query = this.searchQuery.toLowerCase();
144
- virtuals = virtuals.filter(virtual =>
145
- virtual.name.toLowerCase().includes(query)
146
- );
130
+ if (!this.searchQuery.trim()) {
131
+ return paths;
147
132
  }
148
133
 
149
- // Filter by data type
134
+ const query = this.searchQuery.toLowerCase();
135
+ const matches = [];
136
+ const nonMatches = [];
137
+
138
+ paths.forEach(path => {
139
+ if (path.path.toLowerCase().includes(query)) {
140
+ matches.push(path);
141
+ } else {
142
+ nonMatches.push(path);
143
+ }
144
+ });
145
+
146
+ return matches.concat(nonMatches);
147
+ },
148
+ typeFilteredVirtuals() {
149
+ let virtuals = this.virtuals;
150
+
150
151
  if (this.selectedType) {
151
152
  virtuals = virtuals.filter(virtual => {
152
153
  const virtualType = this.getVirtualFieldType(virtual);
@@ -156,6 +157,58 @@ module.exports = app => app.component('document-details', {
156
157
 
157
158
  return virtuals;
158
159
  },
160
+ filteredVirtuals() {
161
+ const virtuals = this.typeFilteredVirtuals.slice();
162
+
163
+ if (!this.searchQuery.trim()) {
164
+ return virtuals;
165
+ }
166
+
167
+ const query = this.searchQuery.toLowerCase();
168
+ const matches = [];
169
+ const nonMatches = [];
170
+
171
+ virtuals.forEach(virtual => {
172
+ if (virtual.name.toLowerCase().includes(query)) {
173
+ matches.push(virtual);
174
+ } else {
175
+ nonMatches.push(virtual);
176
+ }
177
+ });
178
+
179
+ return matches.concat(nonMatches);
180
+ },
181
+ schemaSearchMatchSet() {
182
+ if (!this.searchQuery.trim()) {
183
+ return new Set();
184
+ }
185
+
186
+ const query = this.searchQuery.toLowerCase();
187
+ return new Set(
188
+ this.typeFilteredSchemaPaths
189
+ .filter(path => path.path.toLowerCase().includes(query))
190
+ .map(path => path.path)
191
+ );
192
+ },
193
+ virtualSearchMatchSet() {
194
+ if (!this.searchQuery.trim()) {
195
+ return new Set();
196
+ }
197
+
198
+ const query = this.searchQuery.toLowerCase();
199
+ return new Set(
200
+ this.typeFilteredVirtuals
201
+ .filter(virtual => virtual.name.toLowerCase().includes(query))
202
+ .map(virtual => virtual.name)
203
+ );
204
+ },
205
+ hasSearchMatches() {
206
+ if (!this.searchQuery.trim()) {
207
+ return true;
208
+ }
209
+
210
+ return this.schemaSearchMatchSet.size > 0 || this.virtualSearchMatchSet.size > 0;
211
+ },
159
212
  formattedJson() {
160
213
  if (!this.document) {
161
214
  return '{}';
@@ -171,6 +224,20 @@ module.exports = app => app.component('document-details', {
171
224
  this.collapsedVirtuals.add(fieldName);
172
225
  }
173
226
  },
227
+ isSchemaPathMatched(path) {
228
+ if (!path) {
229
+ return false;
230
+ }
231
+
232
+ return this.schemaSearchMatchSet.has(path.path);
233
+ },
234
+ isVirtualMatched(virtual) {
235
+ if (!virtual) {
236
+ return false;
237
+ }
238
+
239
+ return this.virtualSearchMatchSet.has(virtual.name);
240
+ },
174
241
  isVirtualFieldCollapsed(fieldName) {
175
242
  return this.collapsedVirtuals.has(fieldName);
176
243
  },
@@ -21,18 +21,3 @@
21
21
  float: right;
22
22
  margin-top: -7px;
23
23
  }
24
-
25
- .truncated-value-container,
26
- .expanded-value-container {
27
- position: relative;
28
- }
29
-
30
- .truncated-value-container button,
31
- .expanded-value-container button {
32
- transition: all 0.2s ease;
33
- }
34
-
35
- .truncated-value-container button:hover,
36
- .expanded-value-container button:hover {
37
- transform: translateX(2px);
38
- }
@@ -1,15 +1,16 @@
1
1
  <div class="border border-gray-200 rounded-lg mb-2">
2
2
  <!-- Collapsible Header -->
3
- <div
3
+ <div
4
4
  @click="toggleCollapse"
5
- class="p-3 bg-gray-50 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200"
5
+ class="p-3 hover:bg-gray-100 cursor-pointer flex items-center justify-between border-b border-gray-200 transition-colors duration-200 ease-in-out"
6
+ :class="{ 'bg-amber-100': highlight, 'bg-gray-50': !highlight }"
6
7
  >
7
- <div class="flex items-center">
8
- <svg
8
+ <div class="flex items-center" >
9
+ <svg
9
10
  :class="isCollapsed ? 'rotate-0' : 'rotate-90'"
10
11
  class="w-4 h-4 text-gray-500 mr-2 transition-transform duration-200"
11
- fill="none"
12
- stroke="currentColor"
12
+ fill="none"
13
+ stroke="currentColor"
13
14
  viewBox="0 0 24 24"
14
15
  >
15
16
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
@@ -27,7 +28,7 @@
27
28
  </router-link>
28
29
  </div>
29
30
  </div>
30
-
31
+
31
32
  <!-- Collapsible Content -->
32
33
  <div v-if="!isCollapsed" class="p-3">
33
34
  <!-- Date Type Selector (when editing dates) -->
@@ -53,7 +54,7 @@
53
54
  </div>
54
55
  </div>
55
56
  </div>
56
-
57
+
57
58
  <!-- Field Content -->
58
59
  <div v-if="editting && path.path !== '_id'">
59
60
  <component
@@ -67,11 +68,11 @@
67
68
  </div>
68
69
  <div v-else>
69
70
  <!-- Show truncated or full value based on needsTruncation and isValueExpanded -->
70
- <div v-if="needsTruncation && !isValueExpanded" class="truncated-value-container">
71
+ <div v-if="needsTruncation && !isValueExpanded" class="relative">
71
72
  <div class="text-gray-700 whitespace-pre-wrap break-words font-mono text-sm">{{truncatedString}}</div>
72
- <button
73
+ <button
73
74
  @click="toggleValueExpansion"
74
- class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
75
+ class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
75
76
  >
76
77
  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77
78
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
@@ -79,11 +80,11 @@
79
80
  Show more ({{valueAsString.length}} characters)
80
81
  </button>
81
82
  </div>
82
- <div v-else-if="needsTruncation && isValueExpanded" class="expanded-value-container">
83
+ <div v-else-if="needsTruncation && isValueExpanded" class="relative">
83
84
  <component :is="getComponentForPath(path)" :value="getValueForPath(path.path)"></component>
84
- <button
85
+ <button
85
86
  @click="toggleValueExpansion"
86
- class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1"
87
+ class="mt-2 text-blue-600 hover:text-blue-800 text-sm font-medium flex items-center gap-1 transform transition-all duration-200 ease-in-out hover:translate-x-0.5"
87
88
  >
88
89
  <svg class="w-4 h-4 rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89
90
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
@@ -16,7 +16,7 @@ module.exports = app => app.component('document-property', {
16
16
  isValueExpanded: false // Track if the value is expanded
17
17
  };
18
18
  },
19
- props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid'],
19
+ props: ['path', 'document', 'schemaPaths', 'editting', 'changes', 'invalid', 'highlight'],
20
20
  computed: {
21
21
  valueAsString() {
22
22
  const value = this.getValueForPath(this.path.path);
@@ -94,4 +94,4 @@ module.exports = app => app.component('document-property', {
94
94
  this.isValueExpanded = !this.isValueExpanded;
95
95
  }
96
96
  }
97
- });
97
+ });
@@ -96,3 +96,11 @@
96
96
  .modal-container .modal-exit:hover {
97
97
  background-color: #f1f5ff;
98
98
  }
99
+
100
+ @media (max-width: 767px) {
101
+ .modal-container {
102
+ width: calc(100vw - 10px);
103
+ margin: 0;
104
+ margin-left: 5px;
105
+ }
106
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.0.130",
3
+ "version": "0.0.133",
4
4
  "description": "A sleek, powerful MongoDB UI with built-in dashboarding and auth, seamlessly integrated with your Express, Vercel, or Netlify app.",
5
5
  "homepage": "https://studio.mongoosejs.io/",
6
6
  "repository": {
package/astra.js DELETED
@@ -1,159 +0,0 @@
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. Collections don't let you store keys that start with '$', which is problematic for `$chart`. Ended up creating separate connection to store ChatMessages in MongoDB.
158
- 3. `countDocuments()` with filter erroring out with more than 1000 documents caused trouble. Worked around it by converting `countDocuments()` to `find()` using Mongoose middleware.
159
- */
package/valnotes.md DELETED
@@ -1,2 +0,0 @@
1
- 1. Export to PNG causes charts to scroll down
2
- 2. Chat summarization broken