Skip to content

Commit 1a2da40

Browse files
authored
feat: Add support for asynchronous invocation of FilesAdapter.getFileLocation (#9271)
1 parent 2a63129 commit 1a2da40

File tree

7 files changed

+101
-48
lines changed

7 files changed

+101
-48
lines changed

spec/FilesController.spec.js

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ const mockAdapter = {
2121

2222
// Small additional tests to improve overall coverage
2323
describe('FilesController', () => {
24-
it('should properly expand objects', done => {
24+
it('should properly expand objects with sync getFileLocation', async () => {
2525
const config = Config.get(Parse.applicationId);
2626
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
27+
gridFSAdapter.getFileLocation = (config, filename) => {
28+
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
29+
}
2730
const filesController = new FilesController(gridFSAdapter);
28-
const result = filesController.expandFilesInObject(config, function () {});
31+
const result = await filesController.expandFilesInObject(config, function () { });
2932

3033
expect(result).toBeUndefined();
3134

@@ -37,12 +40,69 @@ describe('FilesController', () => {
3740
const anObject = {
3841
aFile: fullFile,
3942
};
40-
filesController.expandFilesInObject(config, anObject);
43+
await filesController.expandFilesInObject(config, anObject);
4144
expect(anObject.aFile.url).toEqual('http://an.url');
45+
});
4246

43-
done();
47+
it('should properly expand objects with async getFileLocation', async () => {
48+
const config = Config.get(Parse.applicationId);
49+
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
50+
gridFSAdapter.getFileLocation = async (config, filename) => {
51+
await Promise.resolve();
52+
return config.mount + '/files/' + config.applicationId + '/' + encodeURIComponent(filename);
53+
}
54+
const filesController = new FilesController(gridFSAdapter);
55+
const result = await filesController.expandFilesInObject(config, function () { });
56+
57+
expect(result).toBeUndefined();
58+
59+
const fullFile = {
60+
type: '__type',
61+
url: 'http://an.url',
62+
};
63+
64+
const anObject = {
65+
aFile: fullFile,
66+
};
67+
await filesController.expandFilesInObject(config, anObject);
68+
expect(anObject.aFile.url).toEqual('http://an.url');
69+
});
70+
71+
it('should call getFileLocation when config.fileKey is undefined', async () => {
72+
const config = {};
73+
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
74+
75+
const fullFile = {
76+
name: 'mock-name',
77+
__type: 'File',
78+
};
79+
gridFSAdapter.getFileLocation = jasmine.createSpy('getFileLocation').and.returnValue(Promise.resolve('mock-url'));
80+
const filesController = new FilesController(gridFSAdapter);
81+
82+
const anObject = { aFile: fullFile };
83+
await filesController.expandFilesInObject(config, anObject);
84+
expect(gridFSAdapter.getFileLocation).toHaveBeenCalledWith(config, fullFile.name);
85+
expect(anObject.aFile.url).toEqual('mock-url');
4486
});
4587

88+
it('should call getFileLocation when config.fileKey is defined', async () => {
89+
const config = { fileKey: 'mock-key' };
90+
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
91+
92+
const fullFile = {
93+
name: 'mock-name',
94+
__type: 'File',
95+
};
96+
gridFSAdapter.getFileLocation = jasmine.createSpy('getFileLocation').and.returnValue(Promise.resolve('mock-url'));
97+
const filesController = new FilesController(gridFSAdapter);
98+
99+
const anObject = { aFile: fullFile };
100+
await filesController.expandFilesInObject(config, anObject);
101+
expect(gridFSAdapter.getFileLocation).toHaveBeenCalledWith(config, fullFile.name);
102+
expect(anObject.aFile.url).toEqual('mock-url');
103+
});
104+
105+
46106
it_only_db('mongo')('should pass databaseOptions to GridFSBucketAdapter', async () => {
47107
await reconfigureServer({
48108
databaseURI: 'mongodb://localhost:27017/parse',
@@ -101,7 +161,7 @@ describe('FilesController', () => {
101161
done();
102162
});
103163

104-
it('should add a unique hash to the file name when the preserveFileName option is false', done => {
164+
it('should add a unique hash to the file name when the preserveFileName option is false', async () => {
105165
const config = Config.get(Parse.applicationId);
106166
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
107167
spyOn(gridFSAdapter, 'createFile');
@@ -112,17 +172,15 @@ describe('FilesController', () => {
112172
preserveFileName: false,
113173
});
114174

115-
filesController.createFile(config, fileName);
175+
await filesController.createFile(config, fileName);
116176

117177
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
118178
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toMatch(
119179
`^.{32}_${regexEscapedFileName}$`
120180
);
121-
122-
done();
123181
});
124182

125-
it('should not add a unique hash to the file name when the preserveFileName option is true', done => {
183+
it('should not add a unique hash to the file name when the preserveFileName option is true', async () => {
126184
const config = Config.get(Parse.applicationId);
127185
const gridFSAdapter = new GridFSBucketAdapter('mongodb://localhost:27017/parse');
128186
spyOn(gridFSAdapter, 'createFile');
@@ -132,12 +190,10 @@ describe('FilesController', () => {
132190
preserveFileName: true,
133191
});
134192

135-
filesController.createFile(config, fileName);
193+
await filesController.createFile(config, fileName);
136194

137195
expect(gridFSAdapter.createFile).toHaveBeenCalledTimes(1);
138196
expect(gridFSAdapter.createFile.calls.mostRecent().args[0]).toEqual(fileName);
139-
140-
done();
141197
});
142198

143199
it('should handle adapter without getMetadata', async () => {

src/Adapters/Files/FilesAdapter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ export class FilesAdapter {
5959
* @param {Config} config - server configuration
6060
* @param {string} filename
6161
*
62-
* @return {string} Absolute URL
62+
* @return {string | Promise<string>} Absolute URL
6363
*/
64-
getFileLocation(config: Config, filename: string): string {}
64+
getFileLocation(config: Config, filename: string): string | Promise<string> {}
6565

6666
/** Validate a filename for this adapter type
6767
*

src/Controllers/FilesController.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class FilesController extends AdaptableController {
1515
return this.adapter.getFileData(filename);
1616
}
1717

18-
createFile(config, filename, data, contentType, options) {
18+
async createFile(config, filename, data, contentType, options) {
1919
const extname = path.extname(filename);
2020

2121
const hasExtension = extname.length > 0;
@@ -30,13 +30,12 @@ export class FilesController extends AdaptableController {
3030
filename = randomHexString(32) + '_' + filename;
3131
}
3232

33-
const location = this.adapter.getFileLocation(config, filename);
34-
return this.adapter.createFile(filename, data, contentType, options).then(() => {
35-
return Promise.resolve({
36-
url: location,
37-
name: filename,
38-
});
39-
});
33+
const location = await this.adapter.getFileLocation(config, filename);
34+
await this.adapter.createFile(filename, data, contentType, options);
35+
return {
36+
url: location,
37+
name: filename,
38+
}
4039
}
4140

4241
deleteFile(config, filename) {
@@ -55,9 +54,10 @@ export class FilesController extends AdaptableController {
5554
* with the current mount point and app id.
5655
* Object may be a single object or list of REST-format objects.
5756
*/
58-
expandFilesInObject(config, object) {
57+
async expandFilesInObject(config, object) {
5958
if (object instanceof Array) {
60-
object.map(obj => this.expandFilesInObject(config, obj));
59+
const promises = object.map(obj => this.expandFilesInObject(config, obj));
60+
await Promise.all(promises);
6161
return;
6262
}
6363
if (typeof object !== 'object') {
@@ -74,7 +74,7 @@ export class FilesController extends AdaptableController {
7474
// all filenames starting with a "-" seperated UUID should be from files.parse.com
7575
// all other filenames have been migrated or created from Parse Server
7676
if (config.fileKey === undefined) {
77-
fileObject['url'] = this.adapter.getFileLocation(config, filename);
77+
fileObject['url'] = await this.adapter.getFileLocation(config, filename);
7878
} else {
7979
if (filename.indexOf('tfss-') === 0) {
8080
fileObject['url'] =
@@ -83,7 +83,7 @@ export class FilesController extends AdaptableController {
8383
fileObject['url'] =
8484
'http://files.parse.com/' + config.fileKey + '/' + encodeURIComponent(filename);
8585
} else {
86-
fileObject['url'] = this.adapter.getFileLocation(config, filename);
86+
fileObject['url'] = await this.adapter.getFileLocation(config, filename);
8787
}
8888
}
8989
}

src/RestQuery.js

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -735,7 +735,7 @@ _UnsafeRestQuery.prototype.replaceEquality = function () {
735735

736736
// Returns a promise for whether it was successful.
737737
// Populates this.response with an object that only has 'results'.
738-
_UnsafeRestQuery.prototype.runFind = function (options = {}) {
738+
_UnsafeRestQuery.prototype.runFind = async function (options = {}) {
739739
if (this.findOptions.limit === 0) {
740740
this.response = { results: [] };
741741
return Promise.resolve();
@@ -749,24 +749,21 @@ _UnsafeRestQuery.prototype.runFind = function (options = {}) {
749749
if (options.op) {
750750
findOptions.op = options.op;
751751
}
752-
return this.config.database
753-
.find(this.className, this.restWhere, findOptions, this.auth)
754-
.then(results => {
755-
if (this.className === '_User' && !findOptions.explain) {
756-
for (var result of results) {
757-
this.cleanResultAuthData(result);
758-
}
759-
}
752+
const results = await this.config.database.find(this.className, this.restWhere, findOptions, this.auth);
753+
if (this.className === '_User' && !findOptions.explain) {
754+
for (var result of results) {
755+
this.cleanResultAuthData(result);
756+
}
757+
}
760758

761-
this.config.filesController.expandFilesInObject(this.config, results);
759+
await this.config.filesController.expandFilesInObject(this.config, results);
762760

763-
if (this.redirectClassName) {
764-
for (var r of results) {
765-
r.className = this.redirectClassName;
766-
}
767-
}
768-
this.response = { results: results };
769-
});
761+
if (this.redirectClassName) {
762+
for (var r of results) {
763+
r.className = this.redirectClassName;
764+
}
765+
}
766+
this.response = { results: results };
770767
};
771768

772769
// Returns a promise for whether it was successful.

src/RestWrite.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,7 @@ RestWrite.prototype.runBeforeLoginTrigger = async function (userData) {
322322
const extraData = { className: this.className };
323323

324324
// Expand file objects
325-
this.config.filesController.expandFilesInObject(this.config, userData);
325+
await this.config.filesController.expandFilesInObject(this.config, userData);
326326

327327
const user = triggers.inflate(extraData, userData);
328328

@@ -1412,10 +1412,10 @@ RestWrite.prototype.handleInstallation = function () {
14121412
// If we short-circuited the object response - then we need to make sure we expand all the files,
14131413
// since this might not have a query, meaning it won't return the full result back.
14141414
// TODO: (nlutsenko) This should die when we move to per-class based controllers on _Session/_User
1415-
RestWrite.prototype.expandFilesForExistingObjects = function () {
1415+
RestWrite.prototype.expandFilesForExistingObjects = async function () {
14161416
// Check whether we have a short-circuited response - only then run expansion.
14171417
if (this.response && this.response.response) {
1418-
this.config.filesController.expandFilesInObject(this.config, this.response.response);
1418+
await this.config.filesController.expandFilesInObject(this.config, this.response.response);
14191419
}
14201420
};
14211421

src/Routers/FilesRouter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ export class FilesRouter {
263263
const { filename } = req.params;
264264
// run beforeDeleteFile trigger
265265
const file = new Parse.File(filename);
266-
file._url = filesController.adapter.getFileLocation(req.config, filename);
266+
file._url = await filesController.adapter.getFileLocation(req.config, filename);
267267
const fileObject = { file, fileSize: null };
268268
await triggers.maybeRunFileTrigger(
269269
triggers.Types.beforeDelete,

src/Routers/UsersRouter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ export class UsersRouter extends ClassesRouter {
264264
// Remove hidden properties.
265265
UsersRouter.removeHiddenProperties(user);
266266

267-
req.config.filesController.expandFilesInObject(req.config, user);
267+
await req.config.filesController.expandFilesInObject(req.config, user);
268268

269269
// Before login trigger; throws if failure
270270
await maybeRunTrigger(

0 commit comments

Comments
 (0)