feat: Add Cloud Code triggers Parse.Cloud.beforeFind(Parse.File)and Parse.Cloud.afterFind(Parse.File) (#8700)
This commit is contained in:
@@ -3929,6 +3929,126 @@ describe('saveFile hooks', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Parse.File hooks', () => {
|
||||||
|
it('find hooks should run', async () => {
|
||||||
|
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||||
|
await file.save({ useMasterKey: true });
|
||||||
|
const user = await Parse.User.signUp('username', 'password');
|
||||||
|
const hooks = {
|
||||||
|
beforeFind(req) {
|
||||||
|
expect(req).toBeDefined();
|
||||||
|
expect(req.file).toBeDefined();
|
||||||
|
expect(req.triggerName).toBe('beforeFind');
|
||||||
|
expect(req.master).toBeFalse();
|
||||||
|
expect(req.log).toBeDefined();
|
||||||
|
},
|
||||||
|
afterFind(req) {
|
||||||
|
expect(req).toBeDefined();
|
||||||
|
expect(req.file).toBeDefined();
|
||||||
|
expect(req.triggerName).toBe('afterFind');
|
||||||
|
expect(req.master).toBeFalse();
|
||||||
|
expect(req.log).toBeDefined();
|
||||||
|
expect(req.forceDownload).toBeFalse();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const hook in hooks) {
|
||||||
|
spyOn(hooks, hook).and.callThrough();
|
||||||
|
Parse.Cloud[hook](Parse.File, hooks[hook]);
|
||||||
|
}
|
||||||
|
await request({
|
||||||
|
url: file.url(),
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
for (const hook in hooks) {
|
||||||
|
expect(hooks[hook]).toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('beforeFind can throw', async () => {
|
||||||
|
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||||
|
await file.save({ useMasterKey: true });
|
||||||
|
const user = await Parse.User.signUp('username', 'password');
|
||||||
|
const hooks = {
|
||||||
|
beforeFind() {
|
||||||
|
throw 'unauthorized';
|
||||||
|
},
|
||||||
|
afterFind() {},
|
||||||
|
};
|
||||||
|
for (const hook in hooks) {
|
||||||
|
spyOn(hooks, hook).and.callThrough();
|
||||||
|
Parse.Cloud[hook](Parse.File, hooks[hook]);
|
||||||
|
}
|
||||||
|
await expectAsync(
|
||||||
|
request({
|
||||||
|
url: file.url(),
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken(),
|
||||||
|
},
|
||||||
|
}).catch(e => {
|
||||||
|
throw new Parse.Error(e.data.code, e.data.error);
|
||||||
|
})
|
||||||
|
).toBeRejectedWith(new Parse.Error(Parse.Error.SCRIPT_FAILED, 'unauthorized'));
|
||||||
|
|
||||||
|
expect(hooks.beforeFind).toHaveBeenCalled();
|
||||||
|
expect(hooks.afterFind).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('afterFind can throw', async () => {
|
||||||
|
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||||
|
await file.save({ useMasterKey: true });
|
||||||
|
const user = await Parse.User.signUp('username', 'password');
|
||||||
|
const hooks = {
|
||||||
|
beforeFind() {},
|
||||||
|
afterFind() {
|
||||||
|
throw 'unauthorized';
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const hook in hooks) {
|
||||||
|
spyOn(hooks, hook).and.callThrough();
|
||||||
|
Parse.Cloud[hook](Parse.File, hooks[hook]);
|
||||||
|
}
|
||||||
|
await expectAsync(
|
||||||
|
request({
|
||||||
|
url: file.url(),
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken(),
|
||||||
|
},
|
||||||
|
}).catch(e => {
|
||||||
|
throw new Parse.Error(e.data.code, e.data.error);
|
||||||
|
})
|
||||||
|
).toBeRejectedWith(new Parse.Error(Parse.Error.SCRIPT_FAILED, 'unauthorized'));
|
||||||
|
for (const hook in hooks) {
|
||||||
|
expect(hooks[hook]).toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can force download', async () => {
|
||||||
|
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
|
||||||
|
await file.save({ useMasterKey: true });
|
||||||
|
const user = await Parse.User.signUp('username', 'password');
|
||||||
|
Parse.Cloud.afterFind(Parse.File, req => {
|
||||||
|
req.forceDownload = true;
|
||||||
|
});
|
||||||
|
const response = await request({
|
||||||
|
url: file.url(),
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'X-Parse-Session-Token': user.getSessionToken(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.headers['content-disposition']).toBe(`attachment;filename=${file._name}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Cloud Config hooks', () => {
|
describe('Cloud Config hooks', () => {
|
||||||
function testConfig() {
|
function testConfig() {
|
||||||
return Parse.Config.save({ internal: 'i', string: 's', number: 12 }, { internal: true });
|
return Parse.Config.save({ internal: 'i', string: 's', number: 12 }, { internal: true });
|
||||||
|
|||||||
@@ -73,30 +73,68 @@ export class FilesRouter {
|
|||||||
res.json({ code: err.code, error: err.message });
|
res.json({ code: err.code, error: err.message });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const filesController = config.filesController;
|
|
||||||
const filename = req.params.filename;
|
let filename = req.params.filename;
|
||||||
const mime = (await import('mime')).default;
|
try {
|
||||||
const contentType = mime.getType(filename);
|
const filesController = config.filesController;
|
||||||
if (isFileStreamable(req, filesController)) {
|
const mime = (await import('mime')).default;
|
||||||
filesController.handleFileStream(config, filename, req, res, contentType).catch(() => {
|
let contentType = mime.getType(filename);
|
||||||
res.status(404);
|
let file = new Parse.File(filename, { base64: '' }, contentType);
|
||||||
res.set('Content-Type', 'text/plain');
|
const triggerResult = await triggers.maybeRunFileTrigger(
|
||||||
res.end('File not found.');
|
triggers.Types.beforeFind,
|
||||||
});
|
{ file },
|
||||||
} else {
|
config,
|
||||||
filesController
|
req.auth
|
||||||
.getFileData(config, filename)
|
);
|
||||||
.then(data => {
|
if (triggerResult?.file?._name) {
|
||||||
res.status(200);
|
filename = triggerResult?.file?._name;
|
||||||
res.set('Content-Type', contentType);
|
contentType = mime.getType(filename);
|
||||||
res.set('Content-Length', data.length);
|
}
|
||||||
res.end(data);
|
|
||||||
})
|
if (isFileStreamable(req, filesController)) {
|
||||||
.catch(() => {
|
filesController.handleFileStream(config, filename, req, res, contentType).catch(() => {
|
||||||
res.status(404);
|
res.status(404);
|
||||||
res.set('Content-Type', 'text/plain');
|
res.set('Content-Type', 'text/plain');
|
||||||
res.end('File not found.');
|
res.end('File not found.');
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = await filesController.getFileData(config, filename).catch(() => {
|
||||||
|
res.status(404);
|
||||||
|
res.set('Content-Type', 'text/plain');
|
||||||
|
res.end('File not found.');
|
||||||
|
});
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file = new Parse.File(filename, { base64: data.toString('base64') }, contentType);
|
||||||
|
const afterFind = await triggers.maybeRunFileTrigger(
|
||||||
|
triggers.Types.afterFind,
|
||||||
|
{ file, forceDownload: false },
|
||||||
|
config,
|
||||||
|
req.auth
|
||||||
|
);
|
||||||
|
|
||||||
|
if (afterFind?.file) {
|
||||||
|
contentType = mime.getType(afterFind.file._name);
|
||||||
|
data = Buffer.from(afterFind.file._data, 'base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200);
|
||||||
|
res.set('Content-Type', contentType);
|
||||||
|
res.set('Content-Length', data.length);
|
||||||
|
if (afterFind.forceDownload) {
|
||||||
|
res.set('Content-Disposition', `attachment;filename=${afterFind.file._name}`);
|
||||||
|
}
|
||||||
|
res.end(data);
|
||||||
|
} catch (e) {
|
||||||
|
const err = triggers.resolveError(e, {
|
||||||
|
code: Parse.Error.SCRIPT_FAILED,
|
||||||
|
message: `Could not find file: ${filename}.`,
|
||||||
|
});
|
||||||
|
res.status(403);
|
||||||
|
res.json({ code: err.code, error: err.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1004,6 +1004,9 @@ export async function maybeRunFileTrigger(triggerType, fileObject, config, auth)
|
|||||||
return fileObject;
|
return fileObject;
|
||||||
}
|
}
|
||||||
const result = await fileTrigger(request);
|
const result = await fileTrigger(request);
|
||||||
|
if (request.forceDownload) {
|
||||||
|
fileObject.forceDownload = true;
|
||||||
|
}
|
||||||
logTriggerSuccessBeforeHook(
|
logTriggerSuccessBeforeHook(
|
||||||
triggerType,
|
triggerType,
|
||||||
'Parse.File',
|
'Parse.File',
|
||||||
|
|||||||
Reference in New Issue
Block a user