feat: upgrade to MongoDB Node.js driver 4.x for MongoDB 5.0 support (#7794)

BREAKING CHANGE: The MongoDB GridStore adapter has been removed. By default, Parse Server already uses GridFS, so if you do not manually use the GridStore adapter, you can ignore this change.
This commit is contained in:
Antoine Cormouls
2022-02-06 18:30:36 +01:00
committed by GitHub
parent 1299f0697c
commit f88aa2a62a
13 changed files with 392 additions and 668 deletions

View File

@@ -1,6 +1,6 @@
/**
GridFSBucketAdapter
Stores files in Mongo using GridStore
Stores files in Mongo using GridFS
Requires the database adapter to be based on mongoclient
@flow weak

View File

@@ -1,181 +1,4 @@
/**
GridStoreAdapter
Stores files in Mongo using GridStore
Requires the database adapter to be based on mongoclient
(GridStore is deprecated, Please use GridFSBucket instead)
@flow weak
*/
// @flow-disable-next
import { MongoClient, GridStore, Db } from 'mongodb';
import { FilesAdapter, validateFilename } from './FilesAdapter';
import defaults from '../../defaults';
export class GridStoreAdapter extends FilesAdapter {
_databaseURI: string;
_connectionPromise: Promise<Db>;
_mongoOptions: Object;
constructor(mongoDatabaseURI = defaults.DefaultMongoURI, mongoOptions = {}) {
super();
this._databaseURI = mongoDatabaseURI;
const defaultMongoOptions = {
useNewUrlParser: true,
useUnifiedTopology: true,
};
this._mongoOptions = Object.assign(defaultMongoOptions, mongoOptions);
}
_connect() {
if (!this._connectionPromise) {
this._connectionPromise = MongoClient.connect(this._databaseURI, this._mongoOptions).then(
client => {
this._client = client;
return client.db(client.s.options.dbName);
}
);
}
return this._connectionPromise;
}
// For a given config object, filename, and data, store a file
// Returns a promise
createFile(filename: string, data) {
return this._connect()
.then(database => {
const gridStore = new GridStore(database, filename, 'w');
return gridStore.open();
})
.then(gridStore => {
return gridStore.write(data);
})
.then(gridStore => {
return gridStore.close();
});
}
deleteFile(filename: string) {
return this._connect()
.then(database => {
const gridStore = new GridStore(database, filename, 'r');
return gridStore.open();
})
.then(gridStore => {
return gridStore.unlink();
})
.then(gridStore => {
return gridStore.close();
});
}
getFileData(filename: string) {
return this._connect()
.then(database => {
return GridStore.exist(database, filename).then(() => {
const gridStore = new GridStore(database, filename, 'r');
return gridStore.open();
});
})
.then(gridStore => {
return gridStore.read();
});
}
getFileLocation(config, filename) {
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
}
async handleFileStream(filename: string, req, res, contentType) {
const stream = await this._connect().then(database => {
return GridStore.exist(database, filename).then(() => {
const gridStore = new GridStore(database, filename, 'r');
return gridStore.open();
});
});
handleRangeRequest(stream, req, res, contentType);
}
handleShutdown() {
if (!this._client) {
return Promise.resolve();
}
return this._client.close(false);
}
validateFilename(filename) {
return validateFilename(filename);
}
}
// handleRangeRequest is licensed under Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/).
// Author: LEROIB at weightingformypizza (https://weightingformypizza.wordpress.com/2015/06/24/stream-html5-media-content-like-video-audio-from-mongodb-using-express-and-gridstore/).
function handleRangeRequest(stream, req, res, contentType) {
const buffer_size = 1024 * 1024; //1024Kb
// Range request, partial stream the file
const parts = req
.get('Range')
.replace(/bytes=/, '')
.split('-');
let [start, end] = parts;
const notEnded = !end && end !== 0;
const notStarted = !start && start !== 0;
// No end provided, we want all bytes
if (notEnded) {
end = stream.length - 1;
}
// No start provided, we're reading backwards
if (notStarted) {
start = stream.length - end;
end = start + end - 1;
}
// Data exceeds the buffer_size, cap
if (end - start >= buffer_size) {
end = start + buffer_size - 1;
}
const contentLength = end - start + 1;
res.writeHead(206, {
'Content-Range': 'bytes ' + start + '-' + end + '/' + stream.length,
'Accept-Ranges': 'bytes',
'Content-Length': contentLength,
'Content-Type': contentType,
});
stream.seek(start, function () {
// Get gridFile stream
const gridFileStream = stream.stream(true);
let bufferAvail = 0;
let remainingBytesToWrite = contentLength;
let totalBytesWritten = 0;
// Write to response
gridFileStream.on('data', function (data) {
bufferAvail += data.length;
if (bufferAvail > 0) {
// slice returns the same buffer if overflowing
// safe to call in any case
const buffer = data.slice(0, remainingBytesToWrite);
// Write the buffer
res.write(buffer);
// Increment total
totalBytesWritten += buffer.length;
// Decrement remaining
remainingBytesToWrite -= data.length;
// Decrement the available buffer
bufferAvail -= buffer.length;
}
// In case of small slices, all values will be good at that point
// we've written enough, end...
if (totalBytesWritten >= contentLength) {
stream.close();
res.end();
this.destroy();
}
});
});
}
export default GridStoreAdapter;
// Note: GridStore was replaced by GridFSBucketAdapter by default in 2018 by @flovilmart
throw new Error(
'GridStoreAdapter: GridStore is no longer supported by parse server and mongodb, use GridFSBucketAdapter instead.'
);

View File

@@ -177,7 +177,7 @@ class MongoSchemaCollection {
insertSchema(schema: any) {
return this._collection
.insertOne(schema)
.then(result => mongoSchemaToParseSchema(result.ops[0]))
.then(() => mongoSchemaToParseSchema(schema))
.catch(error => {
if (error.code === 11000) {
//Mongo's duplicate key error

View File

@@ -479,6 +479,7 @@ export class MongoStorageAdapter implements StorageAdapter {
const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema);
return this._adaptiveCollection(className)
.then(collection => collection.insertOne(mongoObject, transactionalSession))
.then(() => ({ ops: [mongoObject] }))
.catch(error => {
if (error.code === 11000) {
// Duplicate value
@@ -517,8 +518,8 @@ export class MongoStorageAdapter implements StorageAdapter {
})
.catch(err => this.handleError(err))
.then(
({ result }) => {
if (result.n === 0) {
({ deletedCount }) => {
if (deletedCount === 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
}
return Promise.resolve();