adapt-authoring-mongodb 0.0.1

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/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ node_modules
package/.eslintrc ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "env": {
3
+ "browser": false,
4
+ "node": true,
5
+ "commonjs": false,
6
+ "es2020": true
7
+ },
8
+ "extends": [
9
+ "standard"
10
+ ],
11
+ "parserOptions": {
12
+ "ecmaVersion": 2020
13
+ }
14
+ }
@@ -0,0 +1,55 @@
1
+ name: Bug Report
2
+ description: File a bug report
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for taking the time to fill out this bug report!
9
+ - type: textarea
10
+ id: description
11
+ attributes:
12
+ label: What happened?
13
+ description: Please describe the issue
14
+ validations:
15
+ required: true
16
+ - type: textarea
17
+ id: expected
18
+ attributes:
19
+ label: Expected behaviour
20
+ description: Tell us what should have happened
21
+ - type: textarea
22
+ id: repro-steps
23
+ attributes:
24
+ label: Steps to reproduce
25
+ description: Tell us how to reproduce the issue
26
+ validations:
27
+ required: true
28
+ - type: input
29
+ id: aat-version
30
+ attributes:
31
+ label: Authoring tool version
32
+ description: What version of the Adapt authoring tool are you running?
33
+ validations:
34
+ required: true
35
+ - type: input
36
+ id: fw-version
37
+ attributes:
38
+ label: Framework version
39
+ description: What version of the Adapt framework are you running?
40
+ - type: dropdown
41
+ id: browsers
42
+ attributes:
43
+ label: What browsers are you seeing the problem on?
44
+ multiple: true
45
+ options:
46
+ - Firefox
47
+ - Chrome
48
+ - Safari
49
+ - Microsoft Edge
50
+ - type: textarea
51
+ id: logs
52
+ attributes:
53
+ label: Relevant log output
54
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
55
+ render: sh
@@ -0,0 +1 @@
1
+ blank_issues_enabled: false
@@ -0,0 +1,22 @@
1
+ name: Feature request
2
+ description: Request a new feature
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for taking the time to request a new feature in the Adapt authoring tool! The Adapt team will consider all new feature requests, but unfortunately cannot commit to every one.
9
+ - type: textarea
10
+ id: description
11
+ attributes:
12
+ label: Feature description
13
+ description: Please describe your feature request
14
+ validations:
15
+ required: true
16
+ - type: checkboxes
17
+ id: contribute
18
+ attributes:
19
+ label: Can you work on this feature?
20
+ description: If you are able to commit your own time to work on this feature, it will greatly increase the liklihood of it being implemented by the core dev team. Otherwise, it will be triaged and prioritised alongside the core team's current priorities.
21
+ options:
22
+ - label: I can contribute
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "npm" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
@@ -0,0 +1,25 @@
1
+ [//]: # (Please title your PR according to eslint commit conventions)
2
+ [//]: # (See https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-eslint#eslint-convention for details)
3
+
4
+ [//]: # (Add a link to the original issue)
5
+
6
+ [//]: # (Delete as appropriate)
7
+ ### Fix
8
+ * A sentence describing each fix
9
+
10
+ ### Update
11
+ * A sentence describing each udpate
12
+
13
+ ### New
14
+ * A sentence describing each new feature
15
+
16
+ ### Breaking
17
+ * A sentence describing each breaking change
18
+
19
+ [//]: # (List appropriate steps for testing if needed)
20
+ ### Testing
21
+ 1. Steps for testing
22
+
23
+ [//]: # (Mention any other dependencies)
24
+
25
+
@@ -0,0 +1,16 @@
1
+ name: Add labelled PRs to project
2
+
3
+ on:
4
+ pull_request:
5
+ types: [ labeled ]
6
+
7
+ jobs:
8
+ add-to-project:
9
+ if: ${{ github.event.label.name == 'dependencies' }}
10
+ name: Add to main project
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/add-to-project@v0.1.0
14
+ with:
15
+ project-url: https://github.com/orgs/adapt-security/projects/5
16
+ github-token: ${{ secrets.PROJECTS_SECRET }}
@@ -0,0 +1,19 @@
1
+ name: Add to main project
2
+
3
+ on:
4
+ issues:
5
+ types:
6
+ - opened
7
+ pull_request:
8
+ types:
9
+ - opened
10
+
11
+ jobs:
12
+ add-to-project:
13
+ name: Add to main project
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/add-to-project@v0.1.0
17
+ with:
18
+ project-url: https://github.com/orgs/adapt-security/projects/5
19
+ github-token: ${{ secrets.PROJECTS_SECRET }}
@@ -0,0 +1,9 @@
1
+ {
2
+ "essentialType": "db",
3
+ "documentation": {
4
+ "enable": true,
5
+ "manualPages": {
6
+ "using-mongodb.md": "basics"
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "properties": {
5
+ "connectionUri": {
6
+ "description": "The MongoDB connection URI used to connect to your MongoDB deployment.",
7
+ "type": "string",
8
+ "format": "uri",
9
+ "examples": [
10
+ "mongodb://0.0.0.0/adapt-authoring",
11
+ "mongodb://test1.adaptlearning.org:27018,test2.adaptlearning.org:27019/?replicaSet=test"
12
+ ]
13
+ }
14
+ },
15
+ "required": ["connectionUri"]
16
+ }
@@ -0,0 +1,66 @@
1
+ # Using MongoDB
2
+ The `adapt-authoring-mongodb` module adds the ability to work with MongoDB databases.
3
+
4
+ ## Installing MongoDB
5
+ To save data with the authoring tool, you'll need either a local MongoDB install, or a hosted solution.
6
+
7
+ If you're new to hosting web applications/MongoDB and are intending to make your install accessible via the internet, we'd suggest going with a hosted solution.
8
+
9
+ For instructions on installing MongoDB, see the [MongoDB docs](https://docs.mongodb.com/manual/installation/#mongodb-community-edition-installation-tutorials).
10
+
11
+ ## Using the module
12
+ The `adapt-authoring-mongodb` module uses MongoDB's [Node.js driver](https://mongodb.github.io/node-mongodb-native/4.2) behind-the-scenes for communicating with MongoDB.
13
+
14
+ Where possible, the MongoDBModule API has been designed to mirror the MongoDB Node.js driver API in both naming convensions and parameter naming/order. In some cases this has been changed for ease-of-use (e.g. `insertOne` has been renamed to `insert`). Please see the [Adapt authoring reference for the MongoDBModule](/class/node_modules/adapt-authoring-mongodb/lib/MongoDBModule.js~MongoDBModule.html) for more information (details on which of the MongoDB Node.js driver functions are used is specified there).
15
+
16
+ > If you're new to working with MongoDB, check out this [Quick Start](https://mongodb.github.io/node-mongodb-native/4.2/#quick-start) guide in the official docs for a good overview.
17
+
18
+ ### Basic use
19
+ The following functions provide the most common functionality, and will likely be the functions you use most often. Please see the [API reference]([/class/node_modules/adapt-authoring-mongodb/lib/MongoDBModule.js~MongoDBModule.html](https://tomtaylor.codes/ls/jsdoc3/MongoDBModule.html)) for full details.
20
+
21
+ #### `find(collectionName, query, options)`
22
+ Retrieves a document.
23
+
24
+ #### `insert(collectionName, data, options)`
25
+ Inserts a new document.
26
+
27
+ #### `replace(collectionName, query, data, options)`
28
+ Completely replaces an existing document.
29
+
30
+ #### `update(collectionName, query, data, options)`
31
+ Updates only specific fields of an existing document.
32
+
33
+ #### `delete(collectionName, query, options)`
34
+ Removes an existing document.
35
+
36
+ ### Querying the database
37
+ The `find` function is used to retrieve documents from the database.
38
+
39
+
40
+ #### Examples
41
+ Note that the MongoDB module, as with the rest of the application, makes heavy use of promises, and will return a pending Promise for all relevant functions.
42
+
43
+ Inserting a document into the 'test' collection:
44
+ ```
45
+ try {
46
+ const data = await mongodb.insert('test', { hello: 'world' });
47
+ } catch(e) {
48
+ // handle error
49
+ }
50
+ ```
51
+
52
+ ### Advanced use
53
+ It is possible to access the Node.js driver API directly from the MongoDBModule instance to allow for extra functionality not covered by the MongoDBModule itself. An example of this being creating an aggregation pipeline.
54
+
55
+ There are two methods of accessing the driver API. Which one you use is entirely up to you, and mostly comes down to code brevity:
56
+ - Using the MongoDB client instance [[Adapt docs](https://tomtaylor.codes/ls/jsdoc3/MongoDBModule.html#client), [MongoDB Node.js driver docs](https://mongodb.github.io/node-mongodb-native/3.6/api/MongoClient.html)]
57
+ - Using the MongoDB collection [[Adapt docs](), [MongoDB Node.js driver docs](https://tomtaylor.codes/ls/jsdoc3/MongoDBModule.html#getCollection)]
58
+
59
+ ```
60
+ // using the client instance
61
+ mongodb.client.db.collection('mycollection').aggregate(/* args */)
62
+ // using MongoDBModule#getCollection
63
+ mongodb.getCollection('mycollection').aggregate(/* args */)
64
+ ```
65
+
66
+ See the [MongoDB Node.js driver docs](https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html) for the full API.
@@ -0,0 +1,43 @@
1
+ {
2
+ "INVALID_OBJECTID": {
3
+ "data": {
4
+ "value": "The value"
5
+ },
6
+ "description": "Not a valid ObjectId",
7
+ "statusCode": 400
8
+ },
9
+ "MONGO_CONN_FAILED": {
10
+ "data": {
11
+ "error": "The error message"
12
+ },
13
+ "description": "An error occurred connecting to the MongoDB instance",
14
+ "statusCode": 500
15
+ },
16
+ "MONGO_DUPL_INDEX": {
17
+ "data": {
18
+ "action": "The action being performed on the database collection",
19
+ "collectionName": "Name of the collection being processed",
20
+ "error": "The error message"
21
+ },
22
+ "description": "A document already exists with the same indexed value",
23
+ "statusCode": 400
24
+ },
25
+ "MONGO_ERROR": {
26
+ "data": {
27
+ "action": "The action being performed on the database collection",
28
+ "collectionName": "Name of the collection being processed",
29
+ "error": "The error message"
30
+ },
31
+ "description": "An error occurred while performing a MongoDB action",
32
+ "statusCode": 500
33
+ },
34
+ "MONGO_IMMUTABLE_FIELD": {
35
+ "data": {
36
+ "action": "The action being performed on the database collection",
37
+ "collectionName": "Name of the collection being processed",
38
+ "error": "The error message"
39
+ },
40
+ "description": "Attempting to modify an immutable field",
41
+ "statusCode": 400
42
+ }
43
+ }
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MongoDB integration
3
+ * @namespace mongodb
4
+ */
5
+ export { default as ObjectIdUtils } from './lib/ObjectIdUtils.js'
6
+ export { default } from './lib/MongoDBModule.js'
7
+
@@ -0,0 +1,270 @@
1
+ import { AbstractModule } from 'adapt-authoring-core'
2
+ import { MongoClient } from 'mongodb'
3
+ import ObjectIdUtils from './ObjectIdUtils.js'
4
+ /**
5
+ * Represents a single MongoDB server instance
6
+ * @memberof mongodb
7
+ * @extends {AbstractModule}
8
+ */
9
+ class MongoDBModule extends AbstractModule {
10
+ /** @override */
11
+ async init () {
12
+ await this.app.waitForModule('config')
13
+ /**
14
+ * Reference to the MongDB client
15
+ * @type {external:MongoDBMongoClient}
16
+ */
17
+ this.client = new MongoClient(this.getConfig('connectionUri'), { ignoreUndefined: true })
18
+ await this.connect()
19
+ // add custom keywords to JSON Schema validator
20
+ await ObjectIdUtils.addSchemaKeyword()
21
+ }
22
+
23
+ /**
24
+ * Connects to the database
25
+ * @return {Promise}
26
+ */
27
+ async connect () {
28
+ try {
29
+ await this.client.connect()
30
+ const { hosts, dbName } = this.client.options
31
+ this.log('info', `connected to ${dbName} on ${hosts}`)
32
+ } catch (e) {
33
+ throw this.app.errors.MONGO_CONN_FAILED
34
+ .setData({ error: e.message })
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get all the db statistics
40
+ * @return {Promise}
41
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Db.html#stats
42
+ */
43
+ async getStats () {
44
+ return this.client.db().stats()
45
+ }
46
+
47
+ /**
48
+ * Returns the associated MongoDB collection
49
+ * @param {String} collectionName The name of the MongoDB collection
50
+ * @return {external:MongoDBCollection}
51
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html
52
+ */
53
+ getCollection (collectionName) {
54
+ return this.client.db().collection(collectionName)
55
+ }
56
+
57
+ /**
58
+ * Set an index on a MongoDB collection
59
+ * @param {String} collectionName The name of the MongoDB collection
60
+ * @param {String|Array|Object} fieldOrSpec Definition of the index
61
+ * @param {external:MongoDBCreateIndexesOptions} options Options to pass to the MongoDB driver
62
+ * @return {Promise}
63
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#createIndex
64
+ */
65
+ async setIndex (collectionName, fieldOrSpec, options) {
66
+ try {
67
+ await this.getCollection(collectionName).createIndex(fieldOrSpec, options)
68
+ } catch (e) {
69
+ this.log('warn', e.toString())
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Makes sure options are in the correct format.
75
+ * @param {Object} options The options to parse
76
+ */
77
+ parseOptions (options) {
78
+ if (!options) {
79
+ return
80
+ }
81
+ ['limit', 'skip'].forEach(o => {
82
+ if (options[o] === undefined) return
83
+ try {
84
+ options[o] = parseInt(options[o])
85
+ } catch (e) {
86
+ this.log('warn', `value for option '${o}' is in an unexpected format and will be ignored`)
87
+ delete options[o]
88
+ }
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Adds a new object to the database
94
+ * @param {String} collectionName The name of the MongoDB collection
95
+ * @param {Object} data
96
+ * @param {external:MongoDBInsertOneOptions} options Options to pass to the MongoDB driver
97
+ * @return {Promise} promise
98
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#insertOne
99
+ */
100
+ async insert (collectionName, data, options) {
101
+ this.ObjectId.parseIds(data)
102
+ this.parseOptions(options)
103
+ // MongoDB doesn't like the explicit setting of _id
104
+ delete data._id
105
+ if (data.$set) delete data.$set._id
106
+ try {
107
+ const { insertedId } = await this.getCollection(collectionName).insertOne(data, options)
108
+ const [doc] = await this.find(collectionName, { _id: insertedId })
109
+ return doc
110
+ } catch (e) {
111
+ this.log('error', `failed to insert doc, ${e.message}`)
112
+ throw this.getError(collectionName, 'insert', e)
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Retrieves a new object from the database
118
+ * @param {String} collectionName The name of the MongoDB collection
119
+ * @param {Object} query
120
+ * @param {external:MongoDBFindOptions} options Options to pass to the MongoDB driver
121
+ * @return {Promise} promise
122
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#find
123
+ */
124
+ async find (collectionName, query, options) {
125
+ this.ObjectId.parseIds(query)
126
+ this.parseOptions(options)
127
+ try {
128
+ return await this.getCollection(collectionName).find(query, options).toArray()
129
+ } catch (e) {
130
+ this.log('error', `failed to find docs, ${e.message}`)
131
+ throw this.getError(collectionName, 'find', e)
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Updates an existing object in the database
137
+ * @param {String} collectionName The name of the MongoDB collection
138
+ * @param {Object} query
139
+ * @param {Object} data
140
+ * @param {external:MongoDBFindOneAndUpdateOptions} options Options to pass to the MongoDB driver
141
+ * @return {Promise} promise
142
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#findOneAndUpdate
143
+ */
144
+ async update (collectionName, query, data, options) {
145
+ const opts = Object.assign({ includeResultMetadata: false, returnDocument: 'after' }, options)
146
+ this.parseOptions(opts)
147
+ this.ObjectId.parseIds(query)
148
+ this.ObjectId.parseIds(data)
149
+ // MongoDB doesn't like the explicit setting of _id
150
+ delete data._id
151
+ if (data.$set) delete data.$set._id
152
+ try {
153
+ return await this.getCollection(collectionName).findOneAndUpdate(query, data, opts)
154
+ } catch (e) {
155
+ this.log('error', `failed to update doc, ${e.message}`)
156
+ throw this.getError(collectionName, 'update', e)
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Updates multiple objects in the database
162
+ * @param {String} collectionName The name of the MongoDB collection
163
+ * @param {Object} query
164
+ * @param {external:MongoDBUpdateManyOptions} options Options to pass to the MongoDB driver
165
+ * @return {Promise} promise
166
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#updateMany
167
+ */
168
+ async updateMany (collectionName, query, data, options) {
169
+ this.parseOptions(options)
170
+ this.ObjectId.parseIds(query)
171
+ this.ObjectId.parseIds(data)
172
+ // MongoDB doesn't like the explicit setting of _id
173
+ delete data._id
174
+ if (data.$set) delete data.$set._id
175
+ try {
176
+ await this.getCollection(collectionName).updateMany(query, data, options)
177
+ return this.find(collectionName, query)
178
+ } catch (e) {
179
+ this.log('error', `failed to update docs, ${e.message}`)
180
+ throw this.getError(collectionName, 'update', e)
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Replaces an existing object in the database
186
+ * @param {String} collectionName The name of the MongoDB collection
187
+ * @param {Object} query
188
+ * @param {Object} data
189
+ * @param {external:MongoDBFindOneAndReplaceOptions} options Options to pass to the MongoDB driver
190
+ * @return {Promise} promise
191
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#findOneAndReplace
192
+ */
193
+ async replace (collectionName, query, data, options) {
194
+ const opts = Object.assign({ includeResultMetadata: false, returnDocument: 'after' }, options)
195
+ this.ObjectId.parseIds(query)
196
+ this.ObjectId.parseIds(data)
197
+ this.parseOptions(options)
198
+ // MongoDB doesn't like the explicit setting of _id
199
+ delete data._id
200
+ if (data.$set) delete data.$set._id
201
+ try {
202
+ return await this.getCollection(collectionName).findOneAndReplace(query, data, opts)
203
+ } catch (e) {
204
+ this.log('error', `failed to replace doc, ${e.message}`)
205
+ throw this.getError(collectionName, 'replace', e)
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Removes an existing object from the database
211
+ * @param {String} collectionName The name of the MongoDB collection
212
+ * @param {Object} query
213
+ * @param {external:MongoDBDeleteOptions} options Options to pass to the MongoDB driver
214
+ * @return {Promise} promise
215
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#deleteOne
216
+ */
217
+ async delete (collectionName, query, options) {
218
+ this.ObjectId.parseIds(query)
219
+ this.parseOptions(options)
220
+ try {
221
+ await this.getCollection(collectionName).deleteOne(query, options)
222
+ } catch (e) {
223
+ this.log('error', `failed to delete doc, ${e.message}`)
224
+ throw this.getError(collectionName, 'delete', e)
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Removes multiple objects from the database
230
+ * @param {String} collectionName The name of the MongoDB collection
231
+ * @param {Object} query
232
+ * @param {external:MongoDBDeleteOptions} options Options to pass to the MongoDB driver
233
+ * @return {Promise} promise
234
+ * @see https://mongodb.github.io/node-mongodb-native/4.2/classes/Collection.html#deleteMany
235
+ */
236
+ async deleteMany (collectionName, query, options) {
237
+ this.ObjectId.parseIds(query)
238
+ this.parseOptions(options)
239
+ try {
240
+ await this.getCollection(collectionName).deleteMany(query, options)
241
+ } catch (e) {
242
+ this.log('error', `failed to delete docs, ${e.message}`)
243
+ throw this.getError(collectionName, 'delete', e)
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Returns the relevant AdaptError instance to match the MongoError
249
+ * @param {String} collectionName DB collection being processed
250
+ * @param {String} action DB action being performed
251
+ * @param {String} error The error message
252
+ * @returns {AdaptError}
253
+ */
254
+ getError (collectionName, action, error) {
255
+ let e = this.app.errors.MONGO_ERROR
256
+ if (error.code === 66) e = this.app.errors.MONGO_IMMUTABLE_FIELD
257
+ else if (error.code === 11000) e = this.app.errors.MONGO_DUPL_INDEX
258
+ return e.setData({ collectionName, action, error: error.message })
259
+ }
260
+
261
+ /**
262
+ * ObjectId utility functions
263
+ * @type {ObjectIdUtils}
264
+ */
265
+ get ObjectId () {
266
+ return ObjectIdUtils
267
+ }
268
+ }
269
+
270
+ export default MongoDBModule
@@ -0,0 +1,102 @@
1
+ import { App, Utils } from 'adapt-authoring-core'
2
+ import { ObjectId } from 'mongodb'
3
+ /**
4
+ * Utility functions for dealing with MongoDB ObjectIds
5
+ * @memberof mongodb
6
+ */
7
+ class ObjectIdUtils {
8
+ /**
9
+ * Registers the isObjectId JSON schema keyword
10
+ */
11
+ static async addSchemaKeyword () {
12
+ const jsonschema = await App.instance.waitForModule('jsonschema')
13
+ jsonschema.addKeyword({
14
+ keyword: 'isObjectId',
15
+ type: 'string',
16
+ modifying: true,
17
+ schemaType: 'boolean',
18
+ compile: () => {
19
+ return (value, { parentData, parentDataProperty }) => {
20
+ if (!ObjectIdUtils.isValid(value)) {
21
+ return false
22
+ }
23
+ try {
24
+ parentData[parentDataProperty] = ObjectIdUtils.parse(value)
25
+ } catch (e) {
26
+ return false
27
+ }
28
+ return true
29
+ }
30
+ }
31
+ })
32
+ }
33
+
34
+ /**
35
+ * Creates a new ObjectId instance
36
+ * @return {external:MongoDBObjectId}
37
+ */
38
+ static create () {
39
+ return new ObjectId()
40
+ }
41
+
42
+ /**
43
+ * Checks a string is a valid ObjectId
44
+ * @param {String} s String to check
45
+ * @return {Boolean}
46
+ */
47
+ static isValid (s) {
48
+ try {
49
+ return ObjectIdUtils.parse(s).equals(s)
50
+ } catch (e) {
51
+ return false
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Converts a string to an ObjectId
57
+ * @param {String} s The string to convert
58
+ * @return {external:MongoDBObjectId} The converted ID
59
+ * @throws {Error}
60
+ */
61
+ static parse (s) {
62
+ if (ObjectIdUtils.isObjectId(s)) {
63
+ return s
64
+ }
65
+ if (!ObjectId.isValid(s)) {
66
+ throw App.instance.errors.INVALID_OBJECTID.setData({ value: s })
67
+ }
68
+ return new ObjectId(s)
69
+ }
70
+
71
+ /**
72
+ * Checks an input object for any strings which pass the parse check and convert matches to ObjectId instances
73
+ * @param {Object} o Object to be checked
74
+ */
75
+ static parseIds (o) {
76
+ if (o === undefined) {
77
+ return
78
+ }
79
+ Object.entries(o).forEach(([k, v]) => {
80
+ if (Utils.isObject(v)) {
81
+ this.parseIds(v)
82
+ } else if (Array.isArray(v)) {
83
+ v.forEach((v2, i) => {
84
+ try {
85
+ if (typeof v2 === 'string') v[i] = this.parse(v2)
86
+ } catch (e) {} // ignore failures
87
+ this.parseIds(v2)
88
+ })
89
+ } else if (typeof v === 'string' && this.isValid(v)) {
90
+ try {
91
+ o[k] = this.parse(v)
92
+ } catch (e) {} // ignore failures
93
+ }
94
+ })
95
+ }
96
+
97
+ static isObjectId (data) {
98
+ return data instanceof ObjectId
99
+ }
100
+ }
101
+
102
+ export default ObjectIdUtils
@@ -0,0 +1,63 @@
1
+ /**
2
+ * This file exists to define the below types for documentation purposes.
3
+ */
4
+ /**
5
+ * MongoDB collection
6
+ * @memberof mongodb
7
+ * @external MongoDBCollection
8
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/api/classes/Collection.html}
9
+ */
10
+ /**
11
+ * Options passed to the createIndex function
12
+ * @memberof mongodb
13
+ * @external MongoDBCreateIndexesOptions
14
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/CreateIndexesOptions.html}
15
+ */
16
+ /**
17
+ * Options passed to the delete functions
18
+ * @memberof mongodb
19
+ * @external MongoDBDeleteOptions
20
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/DeleteOptions.html}
21
+ */
22
+ /**
23
+ * Options passed to the findOneAndReplace function
24
+ * @memberof mongodb
25
+ * @external MongoDBFindOneAndReplaceOptions
26
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/FindOneAndReplaceOptions.html}
27
+ */
28
+ /**
29
+ * Options passed to the findOneAndUpdate function
30
+ * @memberof mongodb
31
+ * @external MongoDBFindOneAndUpdateOptions
32
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/FindOneAndUpdateOptions.html}
33
+ */
34
+ /**
35
+ * Options passed to the find function
36
+ * @memberof mongodb
37
+ * @external MongoDBFindOptions
38
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/FindOptions.html}
39
+ */
40
+ /**
41
+ * Options passed to the insertOne function
42
+ * @memberof mongodb
43
+ * @external MongoDBInsertOneOptions
44
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/InsertOneOptions.html}
45
+ */
46
+ /**
47
+ * MongoDB client for DB connections
48
+ * @memberof mongodb
49
+ * @external MongoDBMongoClient
50
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/api/MongoClient.html}
51
+ */
52
+ /**
53
+ * MongoDB representation of BSON ObjectId type
54
+ * @memberof mongodb
55
+ * @external MongoDBObjectId
56
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/api/ObjectID.html}
57
+ */
58
+ /**
59
+ * Options passed to the update function
60
+ * @memberof mongodb
61
+ * @external MongoDBUpdateOptions
62
+ * @see {@link https://mongodb.github.io/node-mongodb-native/4.2/interfaces/UpdateOptions.html}
63
+ */
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "adapt-authoring-mongodb",
3
+ "version": "0.0.1",
4
+ "description": "Module for saving to a MongoDB instance",
5
+ "homepage": "https://github.com/adapt-security/adapt-authoring-mongodb",
6
+ "license": "GPL-3.0",
7
+ "type": "module",
8
+ "main": "index.js",
9
+ "repository": "github:adapt-security/adapt-authoring-mongodb",
10
+ "dependencies": {
11
+ "mongodb": "^6.7.0"
12
+ },
13
+ "peerDependencies": {
14
+ "adapt-authoring-core": "github:adapt-security/adapt-authoring-core"
15
+ },
16
+ "devDependencies": {
17
+ "eslint": "^9.12.0",
18
+ "standard": "^17.1.0"
19
+ }
20
+ }
@@ -0,0 +1,109 @@
1
+ const MongoDBModule = require('../lib/MongoDBModule');
2
+ const should = require('should');
3
+
4
+ describe('MongoDB module', function() {
5
+ describe('#readyState()', function() {
6
+ it('should return a number', function() {
7
+ false.should.be.true();
8
+ });
9
+ it('should return a value even if no connection', function() {
10
+ false.should.be.true();
11
+ });
12
+ });
13
+ describe('#isConnected()', function() {
14
+ it('should return true if connected', function() {
15
+ false.should.be.true();
16
+ });
17
+ it('should return false if not connected', function() {
18
+ false.should.be.true();
19
+ });
20
+ });
21
+ describe('#connectionURI()', function() {
22
+ it('should return a valid connection string', function() {
23
+ false.should.be.true();
24
+ });
25
+ it('should not include undefined values', function() {
26
+ false.should.be.true();
27
+ });
28
+ });
29
+ describe('#getCollection()', function() {
30
+ it('should return a MongoDB collection', function() {
31
+ false.should.be.true();
32
+ });
33
+ });
34
+ describe('#connect()', function() {
35
+ it('should establish a connection to specified MongoDB instance', function() {
36
+ false.should.be.true();
37
+ });
38
+ it('should fail gracefully on error', function() {
39
+ false.should.be.true();
40
+ });
41
+ });
42
+ describe('#insert()', function() {
43
+ it('should return a promise', function() {
44
+ false.should.be.true();
45
+ });
46
+ it('should return an error if no data is passed', function() {
47
+ false.should.be.true();
48
+ });
49
+ it('should return the inserted document', function() {
50
+ false.should.be.true();
51
+ });
52
+ });
53
+ describe('#find()', function() {
54
+ it('should return a promise', function() {
55
+ false.should.be.true();
56
+ });
57
+ it('should return an error if invalid query data is passed', function() {
58
+ false.should.be.true();
59
+ });
60
+ it('should return an error if invalid model type is specified', function() {
61
+ false.should.be.true();
62
+ });
63
+ it('should populate specified attributes', function() {
64
+ false.should.be.true();
65
+ });
66
+ it('should return matching documents', function() {
67
+ false.should.be.true();
68
+ });
69
+ });
70
+ describe('#replace()', function() {
71
+ it('should return a promise', function() {
72
+ false.should.be.true();
73
+ });
74
+ it('should return an error if invalid query data is passed', function() {
75
+ false.should.be.true();
76
+ });
77
+ it('should return an error if invalid model type is specified', function() {
78
+ false.should.be.true();
79
+ });
80
+ it('should replace matching document(s)', function() {
81
+ false.should.be.true();
82
+ });
83
+ });
84
+ describe('#delete()', function() {
85
+ it('should return a promise', function() {
86
+ false.should.be.true();
87
+ });
88
+ it('should return an error if invalid query data is passed', function() {
89
+ false.should.be.true();
90
+ });
91
+ it('should return an error if invalid model type is specified', function() {
92
+ false.should.be.true();
93
+ });
94
+ it('should delete matching document(s)', function() {
95
+ false.should.be.true();
96
+ });
97
+ });
98
+ describe('#formatError()', function() {
99
+ it('should return an Error', function() {
100
+ false.should.be.true();
101
+ });
102
+ it('should set custom statusCode', function() {
103
+ false.should.be.true();
104
+ });
105
+ it('should set default statusCode', function() {
106
+ false.should.be.true();
107
+ });
108
+ });
109
+ });
@@ -0,0 +1,37 @@
1
+ const should = require('should');
2
+
3
+ describe('ObjectId Utils', function() {
4
+ describe('#isValid', function() {
5
+ it('should return false for empty/undefined param', function() {
6
+ false.should.be.true();
7
+ });
8
+ it('should return true for valid string', function() {
9
+ false.should.be.true();
10
+ });
11
+ it('should return true for invalid type', function() {
12
+ false.should.be.true();
13
+ });
14
+ it('should return true for invalid string', function() {
15
+ false.should.be.true();
16
+ });
17
+ });
18
+ describe('#parse', function() {
19
+ it('should return an ObjectId instance', function() {
20
+ false.should.be.true();
21
+ });
22
+ it('should throw an error on invalid input', function() {
23
+ false.should.be.true();
24
+ });
25
+ });
26
+ describe('#parseParamIds', function() {
27
+ it('parse a valid _id to an ObjectId', function() {
28
+ false.should.be.true();
29
+ });
30
+ it('throw an error on invalid _id', function() {
31
+ false.should.be.true();
32
+ });
33
+ it('ignore non _id fields', function() {
34
+ false.should.be.true();
35
+ });
36
+ });
37
+ });