feat: Add support for asynchronous invocation of FilesAdapter.getFileLocation (#9271)
This commit is contained in:
@@ -21,11 +21,14 @@ const mockAdapter = {
|
|||||||
|
|
||||||
// Small additional tests to improve overall coverage
|
// Small additional tests to improve overall coverage
|
||||||
describe('FilesController', () => {
|
describe('FilesController', () => {
|
||||||
it('should properly expand objects', done => {
|
it('should properly expand objects with sync getFileLocation', async () => {
|
||||||
const config = Config.get(Parse.applicationId);
|
const config = Config.get(Parse.applicationId);
|
||||||
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||||
|
gridFSAdapter.getFileLocation = (config, filename) => {
|
||||||
|
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
|
||||||
|
}
|
||||||
const filesController = new FilesController(gridFSAdapter);
|
const filesController = new FilesController(gridFSAdapter);
|
||||||
const result = filesController.expandFilesInObject(config, function () {});
|
const result = await filesController.expandFilesInObject(config, function () { });
|
||||||
|
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
|
|
||||||
@@ -37,12 +40,69 @@ describe('FilesController', () => {
|
|||||||
const anObject = {
|
const anObject = {
|
||||||
aFile: fullFile,
|
aFile: fullFile,
|
||||||
};
|
};
|
||||||
filesController.expandFilesInObject(config, anObject);
|
await filesController.expandFilesInObject(config, anObject);
|
||||||
expect(anObject.aFile.url).toEqual('http://an.url');
|
expect(anObject.aFile.url).toEqual('http://an.url');
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should properly expand objects with async getFileLocation', async () => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||||
|
gridFSAdapter.getFileLocation = async (config, filename) => {
|
||||||
|
await Promise.resolve();
|
||||||
|
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
|
||||||
|
}
|
||||||
|
const filesController = new FilesController(gridFSAdapter);
|
||||||
|
const result = await filesController.expandFilesInObject(config, function () { });
|
||||||
|
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
|
||||||
|
const fullFile = {
|
||||||
|
type: '__type',
|
||||||
|
url: 'http://an.url',
|
||||||
|
};
|
||||||
|
|
||||||
|
const anObject = {
|
||||||
|
aFile: fullFile,
|
||||||
|
};
|
||||||
|
await filesController.expandFilesInObject(config, anObject);
|
||||||
|
expect(anObject.aFile.url).toEqual('http://an.url');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getFileLocation when config.fileKey is undefined', async () => {
|
||||||
|
const config = {};
|
||||||
|
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||||
|
|
||||||
|
const fullFile = {
|
||||||
|
name: 'mock-name',
|
||||||
|
__type: 'File',
|
||||||
|
};
|
||||||
|
gridFSAdapter.getFileLocation = jasmine.createSpy('getFileLocation').and.returnValue(Promise.resolve('mock-url'));
|
||||||
|
const filesController = new FilesController(gridFSAdapter);
|
||||||
|
|
||||||
|
const anObject = { aFile: fullFile };
|
||||||
|
await filesController.expandFilesInObject(config, anObject);
|
||||||
|
expect(gridFSAdapter.getFileLocation).toHaveBeenCalledWith(config, fullFile.name);
|
||||||
|
expect(anObject.aFile.url).toEqual('mock-url');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getFileLocation when config.fileKey is defined', async () => {
|
||||||
|
const config = { fileKey: 'mock-key' };
|
||||||
|
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||||
|
|
||||||
|
const fullFile = {
|
||||||
|
name: 'mock-name',
|
||||||
|
__type: 'File',
|
||||||
|
};
|
||||||
|
gridFSAdapter.getFileLocation = jasmine.createSpy('getFileLocation').and.returnValue(Promise.resolve('mock-url'));
|
||||||
|
const filesController = new FilesController(gridFSAdapter);
|
||||||
|
|
||||||
|
const anObject = { aFile: fullFile };
|
||||||
|
await filesController.expandFilesInObject(config, anObject);
|
||||||
|
expect(gridFSAdapter.getFileLocation).toHaveBeenCalledWith(config, fullFile.name);
|
||||||
|
expect(anObject.aFile.url).toEqual('mock-url');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
it_only_db('mongo')('should pass databaseOptions to GridFSBucketAdapter', async () => {
|
it_only_db('mongo')('should pass databaseOptions to GridFSBucketAdapter', async () => {
|
||||||
await reconfigureServer({
|
await reconfigureServer({
|
||||||
databaseURI: 'mongodb://localhost:27017/parse',
|
databaseURI: 'mongodb://localhost:27017/parse',
|
||||||
@@ -101,7 +161,7 @@ describe('FilesController', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should add a unique hash to the file name when the preserveFileName option is false', done => {
|
it('should add a unique hash to the file name when the preserveFileName option is false', async () => {
|
||||||
const config = Config.get(Parse.applicationId);
|
const config = Config.get(Parse.applicationId);
|
||||||
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||||
spyOn(gridFSAdapter, 'createFile');
|
spyOn(gridFSAdapter, 'createFile');
|
||||||
@@ -112,17 +172,15 @@ describe('FilesController', () => {
|
|||||||
preserveFileName: false,
|
preserveFileName: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
filesController.createFile(config, fileName);
|
await filesController.createFile(config, fileName);
|
||||||
|
|
||||||
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
|
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||||
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toMatch(
|
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toMatch(
|
||||||
`^.{32}_${regexEscapedFileName}$`
|
`^.{32}_${regexEscapedFileName}$`
|
||||||
);
|
);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not add a unique hash to the file name when the preserveFileName option is true', done => {
|
it('should not add a unique hash to the file name when the preserveFileName option is true', async () => {
|
||||||
const config = Config.get(Parse.applicationId);
|
const config = Config.get(Parse.applicationId);
|
||||||
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
|
||||||
spyOn(gridFSAdapter, 'createFile');
|
spyOn(gridFSAdapter, 'createFile');
|
||||||
@@ -132,12 +190,10 @@ describe('FilesController', () => {
|
|||||||
preserveFileName: true,
|
preserveFileName: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
filesController.createFile(config, fileName);
|
await filesController.createFile(config, fileName);
|
||||||
|
|
||||||
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
|
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
|
||||||
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toEqual(fileName);
|
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toEqual(fileName);
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle adapter without getMetadata', async () => {
|
it('should handle adapter without getMetadata', async () => {
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ export class FilesAdapter {
|
|||||||
* @param {Config} config - server configuration
|
* @param {Config} config - server configuration
|
||||||
* @param {string} filename
|
* @param {string} filename
|
||||||
*
|
*
|
||||||
* @return {string} Absolute URL
|
* @return {string | Promise<string>} Absolute URL
|
||||||
*/
|
*/
|
||||||
getFileLocation(config: Config, filename: string): string {}
|
getFileLocation(config: Config, filename: string): string | Promise<string> {}
|
||||||
|
|
||||||
/** Validate a filename for this adapter type
|
/** Validate a filename for this adapter type
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class FilesController extends AdaptableController {
|
|||||||
return this.adapter.getFileData(filename);
|
return this.adapter.getFileData(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
createFile(config, filename, data, contentType, options) {
|
async createFile(config, filename, data, contentType, options) {
|
||||||
const extname = path.extname(filename);
|
const extname = path.extname(filename);
|
||||||
|
|
||||||
const hasExtension = extname.length > 0;
|
const hasExtension = extname.length > 0;
|
||||||
@@ -30,13 +30,12 @@ export class FilesController extends AdaptableController {
|
|||||||
filename = randomHexString(32) + '_' + filename;
|
filename = randomHexString(32) + '_' + filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
const location = this.adapter.getFileLocation(config, filename);
|
const location = await this.adapter.getFileLocation(config, filename);
|
||||||
return this.adapter.createFile(filename, data, contentType, options).then(() => {
|
await this.adapter.createFile(filename, data, contentType, options);
|
||||||
return Promise.resolve({
|
return {
|
||||||
url: location,
|
url: location,
|
||||||
name: filename,
|
name: filename,
|
||||||
});
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteFile(config, filename) {
|
deleteFile(config, filename) {
|
||||||
@@ -55,9 +54,10 @@ export class FilesController extends AdaptableController {
|
|||||||
* with the current mount point and app id.
|
* with the current mount point and app id.
|
||||||
* Object may be a single object or list of REST-format objects.
|
* Object may be a single object or list of REST-format objects.
|
||||||
*/
|
*/
|
||||||
expandFilesInObject(config, object) {
|
async expandFilesInObject(config, object) {
|
||||||
if (object instanceof Array) {
|
if (object instanceof Array) {
|
||||||
object.map(obj => this.expandFilesInObject(config, obj));
|
const promises = object.map(obj => this.expandFilesInObject(config, obj));
|
||||||
|
await Promise.all(promises);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof object !== 'object') {
|
if (typeof object !== 'object') {
|
||||||
@@ -74,7 +74,7 @@ export class FilesController extends AdaptableController {
|
|||||||
// all filenames starting with a "-" seperated UUID should be from files.parse.com
|
// all filenames starting with a "-" seperated UUID should be from files.parse.com
|
||||||
// all other filenames have been migrated or created from Parse Server
|
// all other filenames have been migrated or created from Parse Server
|
||||||
if (config.fileKey === undefined) {
|
if (config.fileKey === undefined) {
|
||||||
fileObject['url'] = this.adapter.getFileLocation(config, filename);
|
fileObject['url'] = await this.adapter.getFileLocation(config, filename);
|
||||||
} else {
|
} else {
|
||||||
if (filename.indexOf('tfss-') === 0) {
|
if (filename.indexOf('tfss-') === 0) {
|
||||||
fileObject['url'] =
|
fileObject['url'] =
|
||||||
@@ -83,7 +83,7 @@ export class FilesController extends AdaptableController {
|
|||||||
fileObject['url'] =
|
fileObject['url'] =
|
||||||
'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename);
|
'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename);
|
||||||
} else {
|
} else {
|
||||||
fileObject['url'] = this.adapter.getFileLocation(config, filename);
|
fileObject['url'] = await this.adapter.getFileLocation(config, filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -735,7 +735,7 @@ _UnsafeRestQuery.prototype.replaceEquality = function () {
|
|||||||
|
|
||||||
// Returns a promise for whether it was successful.
|
// Returns a promise for whether it was successful.
|
||||||
// Populates this.response with an object that only has 'results'.
|
// Populates this.response with an object that only has 'results'.
|
||||||
_UnsafeRestQuery.prototype.runFind = function (options = {}) {
|
_UnsafeRestQuery.prototype.runFind = async function (options = {}) {
|
||||||
if (this.findOptions.limit === 0) {
|
if (this.findOptions.limit === 0) {
|
||||||
this.response = { results: [] };
|
this.response = { results: [] };
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -749,24 +749,21 @@ _UnsafeRestQuery.prototype.runFind = function (options = {}) {
|
|||||||
if (options.op) {
|
if (options.op) {
|
||||||
findOptions.op = options.op;
|
findOptions.op = options.op;
|
||||||
}
|
}
|
||||||
return this.config.database
|
const results = await this.config.database.find(this.className, this.restWhere, findOptions, this.auth);
|
||||||
.find(this.className, this.restWhere, findOptions, this.auth)
|
if (this.className === '_User' && !findOptions.explain) {
|
||||||
.then(results => {
|
for (var result of results) {
|
||||||
if (this.className === '_User' && !findOptions.explain) {
|
this.cleanResultAuthData(result);
|
||||||
for (var result of results) {
|
}
|
||||||
this.cleanResultAuthData(result);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.config.filesController.expandFilesInObject(this.config, results);
|
await this.config.filesController.expandFilesInObject(this.config, results);
|
||||||
|
|
||||||
if (this.redirectClassName) {
|
if (this.redirectClassName) {
|
||||||
for (var r of results) {
|
for (var r of results) {
|
||||||
r.className = this.redirectClassName;
|
r.className = this.redirectClassName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.response = { results: results };
|
this.response = { results: results };
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a promise for whether it was successful.
|
// Returns a promise for whether it was successful.
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ RestWrite.prototype.runBeforeLoginTrigger = async function (userData) {
|
|||||||
const extraData = { className: this.className };
|
const extraData = { className: this.className };
|
||||||
|
|
||||||
// Expand file objects
|
// Expand file objects
|
||||||
this.config.filesController.expandFilesInObject(this.config, userData);
|
await this.config.filesController.expandFilesInObject(this.config, userData);
|
||||||
|
|
||||||
const user = triggers.inflate(extraData, userData);
|
const user = triggers.inflate(extraData, userData);
|
||||||
|
|
||||||
@@ -1412,10 +1412,10 @@ RestWrite.prototype.handleInstallation = function () {
|
|||||||
// If we short-circuited the object response - then we need to make sure we expand all the files,
|
// If we short-circuited the object response - then we need to make sure we expand all the files,
|
||||||
// since this might not have a query, meaning it won't return the full result back.
|
// since this might not have a query, meaning it won't return the full result back.
|
||||||
// TODO: (nlutsenko) This should die when we move to per-class based controllers on _Session/_User
|
// TODO: (nlutsenko) This should die when we move to per-class based controllers on _Session/_User
|
||||||
RestWrite.prototype.expandFilesForExistingObjects = function () {
|
RestWrite.prototype.expandFilesForExistingObjects = async function () {
|
||||||
// Check whether we have a short-circuited response - only then run expansion.
|
// Check whether we have a short-circuited response - only then run expansion.
|
||||||
if (this.response && this.response.response) {
|
if (this.response && this.response.response) {
|
||||||
this.config.filesController.expandFilesInObject(this.config, this.response.response);
|
await this.config.filesController.expandFilesInObject(this.config, this.response.response);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ export class FilesRouter {
|
|||||||
const { filename } = req.params;
|
const { filename } = req.params;
|
||||||
// run beforeDeleteFile trigger
|
// run beforeDeleteFile trigger
|
||||||
const file = new Parse.File(filename);
|
const file = new Parse.File(filename);
|
||||||
file._url = filesController.adapter.getFileLocation(req.config, filename);
|
file._url = await filesController.adapter.getFileLocation(req.config, filename);
|
||||||
const fileObject = { file, fileSize: null };
|
const fileObject = { file, fileSize: null };
|
||||||
await triggers.maybeRunFileTrigger(
|
await triggers.maybeRunFileTrigger(
|
||||||
triggers.Types.beforeDelete,
|
triggers.Types.beforeDelete,
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ export class UsersRouter extends ClassesRouter {
|
|||||||
// Remove hidden properties.
|
// Remove hidden properties.
|
||||||
UsersRouter.removeHiddenProperties(user);
|
UsersRouter.removeHiddenProperties(user);
|
||||||
|
|
||||||
req.config.filesController.expandFilesInObject(req.config, user);
|
await req.config.filesController.expandFilesInObject(req.config, user);
|
||||||
|
|
||||||
// Before login trigger; throws if failure
|
// Before login trigger; throws if failure
|
||||||
await maybeRunTrigger(
|
await maybeRunTrigger(
|
||||||
|
|||||||
Reference in New Issue
Block a user