Merge pull request #354 from westhom/file-deletion
support file DELETEs
This commit is contained in:
@@ -33,6 +33,95 @@ describe('Parse.File testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('supports REST end-to-end file create, read, delete, read', done => {
|
||||||
|
var headers = {
|
||||||
|
'Content-Type': 'image/jpeg',
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest'
|
||||||
|
};
|
||||||
|
request.post({
|
||||||
|
headers: headers,
|
||||||
|
url: 'http://localhost:8378/1/files/testfile.txt',
|
||||||
|
body: 'check one two',
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
var b = JSON.parse(body);
|
||||||
|
expect(b.name).toMatch(/_testfile.txt$/);
|
||||||
|
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*testfile.txt$/);
|
||||||
|
request.get(b.url, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
expect(body).toEqual('check one two');
|
||||||
|
request.del({
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'X-Parse-Master-Key': 'test'
|
||||||
|
},
|
||||||
|
url: 'http://localhost:8378/1/files/' + b.name
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
expect(response.statusCode).toEqual(200);
|
||||||
|
request.get({
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest'
|
||||||
|
},
|
||||||
|
url: b.url
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
expect(response.statusCode).toEqual(404);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blocks file deletions with missing or incorrect master-key header', done => {
|
||||||
|
var headers = {
|
||||||
|
'Content-Type': 'image/jpeg',
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest'
|
||||||
|
};
|
||||||
|
request.post({
|
||||||
|
headers: headers,
|
||||||
|
url: 'http://localhost:8378/1/files/thefile.jpg',
|
||||||
|
body: 'the file body'
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
var b = JSON.parse(body);
|
||||||
|
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*thefile.jpg$/);
|
||||||
|
// missing X-Parse-Master-Key header
|
||||||
|
request.del({
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest'
|
||||||
|
},
|
||||||
|
url: 'http://localhost:8378/1/files/' + b.name
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
var del_b = JSON.parse(body);
|
||||||
|
expect(response.statusCode).toEqual(400);
|
||||||
|
expect(del_b.code).toEqual(119);
|
||||||
|
// incorrect X-Parse-Master-Key header
|
||||||
|
request.del({
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'test',
|
||||||
|
'X-Parse-REST-API-Key': 'rest',
|
||||||
|
'X-Parse-Master-Key': 'tryagain'
|
||||||
|
},
|
||||||
|
url: 'http://localhost:8378/1/files/' + b.name
|
||||||
|
}, (error, response, body) => {
|
||||||
|
expect(error).toBe(null);
|
||||||
|
var del_b2 = JSON.parse(body);
|
||||||
|
expect(response.statusCode).toEqual(400);
|
||||||
|
expect(del_b2.code).toEqual(119);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('handles other filetypes', done => {
|
it('handles other filetypes', done => {
|
||||||
var headers = {
|
var headers = {
|
||||||
'Content-Type': 'image/jpeg',
|
'Content-Type': 'image/jpeg',
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
export class FilesAdapter {
|
export class FilesAdapter {
|
||||||
createFile(config, filename, data) { }
|
createFile(config, filename, data) { }
|
||||||
|
|
||||||
|
deleteFile(config, filename) { }
|
||||||
|
|
||||||
getFileData(config, filename) { }
|
getFileData(config, filename) { }
|
||||||
|
|
||||||
getFileLocation(config, filename) { }
|
getFileLocation(config, filename) { }
|
||||||
|
|||||||
@@ -20,6 +20,17 @@ export class GridStoreAdapter extends FilesAdapter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteFile(config, filename) {
|
||||||
|
return config.database.connect().then(() => {
|
||||||
|
let gridStore = new GridStore(config.database.db, filename, 'w');
|
||||||
|
return gridStore.open();
|
||||||
|
}).then((gridStore) => {
|
||||||
|
return gridStore.unlink();
|
||||||
|
}).then((gridStore) => {
|
||||||
|
return gridStore.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getFileData(config, filename) {
|
getFileData(config, filename) {
|
||||||
return config.database.connect().then(() => {
|
return config.database.connect().then(() => {
|
||||||
return GridStore.exist(config.database.db, filename);
|
return GridStore.exist(config.database.db, filename);
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ export class S3Adapter extends FilesAdapter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteFile(config, filename) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let params = {
|
||||||
|
Key: this._bucketPrefix + filename
|
||||||
|
};
|
||||||
|
this._s3Client.deleteObject(params, (err, data) =>{
|
||||||
|
if(err !== null) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(data);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Search for and return a file if found by filename
|
// Search for and return a file if found by filename
|
||||||
// Returns a promise that succeeds with the buffer result from S3
|
// Returns a promise that succeeds with the buffer result from S3
|
||||||
getFileData(config, filename) {
|
getFileData(config, filename) {
|
||||||
|
|||||||
@@ -74,6 +74,26 @@ export class FilesController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteHandler() {
|
||||||
|
return (req, res, next) => {
|
||||||
|
// enforce use of master key for file deletions
|
||||||
|
if(!req.auth.isMaster){
|
||||||
|
next(new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
||||||
|
'Master key required for file deletion.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._filesAdapter.deleteFile(req.config, req.params.filename).then(() => {
|
||||||
|
res.status(200);
|
||||||
|
// TODO: return useful JSON here?
|
||||||
|
res.end();
|
||||||
|
}).catch((error) => {
|
||||||
|
next(new Parse.Error(Parse.Error.FILE_DELETE_ERROR,
|
||||||
|
'Could not delete file.'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find file references in REST-format object and adds the url key
|
* Find file references in REST-format object and adds the url key
|
||||||
* with the current mount point and app id.
|
* with the current mount point and app id.
|
||||||
@@ -119,6 +139,12 @@ export class FilesController {
|
|||||||
this.createHandler()
|
this.createHandler()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete('/files/:filename',
|
||||||
|
Middlewares.allowCrossDomain,
|
||||||
|
Middlewares.handleParseHeaders,
|
||||||
|
this.deleteHandler()
|
||||||
|
);
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user