Skip to content

Commit b2a5092

Browse files
Marco129drew-gross
authored andcommitted
Endpoint for purging all objects in class (#2032)
* Endpoint for purging all objects in class * Use deleteObjectsByQuery * Standalone handling function and purge cache * Change endpoint url
1 parent d9f8486 commit b2a5092

File tree

5 files changed

+145
-1
lines changed

5 files changed

+145
-1
lines changed

spec/ParseAPI.spec.js

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
var DatabaseAdapter = require('../src/DatabaseAdapter');
66
const MongoStorageAdapter = require('../src/Adapters/Storage/Mongo/MongoStorageAdapter');
77
var request = require('request');
8+
const rp = require('request-promise');
89
const Parse = require("parse/node");
910
let Config = require('../src/Config');
1011
let defaultColumns = require('../src/Controllers/SchemaController').defaultColumns;
@@ -1347,4 +1348,111 @@ describe('miscellaneous', function() {
13471348
done();
13481349
});
13491350
});
1351+
1352+
it('purge all objects in class', (done) => {
1353+
let object = new Parse.Object('TestObject');
1354+
object.set('foo', 'bar');
1355+
let object2 = new Parse.Object('TestObject');
1356+
object2.set('alice', 'wonderland');
1357+
Parse.Object.saveAll([object, object2])
1358+
.then(() => {
1359+
let query = new Parse.Query(TestObject);
1360+
return query.count()
1361+
}).then((count) => {
1362+
expect(count).toBe(2);
1363+
let headers = {
1364+
'Content-Type': 'application/json',
1365+
'X-Parse-Application-Id': 'test',
1366+
'X-Parse-Master-Key': 'test'
1367+
};
1368+
request.del({
1369+
headers: headers,
1370+
url: 'http://localhost:8378/1/purge/TestObject',
1371+
json: true
1372+
}, (err, res, body) => {
1373+
expect(err).toBe(null);
1374+
let query = new Parse.Query(TestObject);
1375+
return query.count().then((count) => {
1376+
expect(count).toBe(0);
1377+
done();
1378+
});
1379+
});
1380+
});
1381+
});
1382+
1383+
it('fail on purge all objects in class without master key', (done) => {
1384+
let headers = {
1385+
'Content-Type': 'application/json',
1386+
'X-Parse-Application-Id': 'test',
1387+
'X-Parse-REST-API-Key': 'rest'
1388+
};
1389+
rp({
1390+
method: 'DELETE',
1391+
headers: headers,
1392+
uri: 'http://localhost:8378/1/purge/TestObject',
1393+
json: true
1394+
}).then(body => {
1395+
fail('Should not succeed');
1396+
}).catch(err => {
1397+
expect(err.error.error).toEqual('unauthorized: master key is required');
1398+
done();
1399+
});
1400+
});
1401+
1402+
it('purge all objects in _Role also purge cache', (done) => {
1403+
let headers = {
1404+
'Content-Type': 'application/json',
1405+
'X-Parse-Application-Id': 'test',
1406+
'X-Parse-Master-Key': 'test'
1407+
};
1408+
var user, object;
1409+
createTestUser().then((x) => {
1410+
user = x;
1411+
let acl = new Parse.ACL();
1412+
acl.setPublicReadAccess(true);
1413+
acl.setPublicWriteAccess(false);
1414+
let role = new Parse.Object('_Role');
1415+
role.set('name', 'TestRole');
1416+
role.setACL(acl);
1417+
let users = role.relation('users');
1418+
users.add(user);
1419+
return role.save({}, { useMasterKey: true });
1420+
}).then((x) => {
1421+
let query = new Parse.Query('_Role');
1422+
return query.find({ useMasterKey: true });
1423+
}).then((x) => {
1424+
expect(x.length).toEqual(1);
1425+
let relation = x[0].relation('users').query();
1426+
return relation.first({ useMasterKey: true });
1427+
}).then((x) => {
1428+
expect(x.id).toEqual(user.id);
1429+
object = new Parse.Object('TestObject');
1430+
let acl = new Parse.ACL();
1431+
acl.setPublicReadAccess(false);
1432+
acl.setPublicWriteAccess(false);
1433+
acl.setRoleReadAccess('TestRole', true);
1434+
acl.setRoleWriteAccess('TestRole', true);
1435+
object.setACL(acl);
1436+
return object.save();
1437+
}).then((x) => {
1438+
let query = new Parse.Query('TestObject');
1439+
return query.find({ sessionToken: user.getSessionToken() });
1440+
}).then((x) => {
1441+
expect(x.length).toEqual(1);
1442+
return rp({
1443+
method: 'DELETE',
1444+
headers: headers,
1445+
uri: 'http://localhost:8378/1/purge/_Role',
1446+
json: true
1447+
});
1448+
}).then((x) => {
1449+
let query = new Parse.Query('TestObject');
1450+
return query.get(object.id, { sessionToken: user.getSessionToken() });
1451+
}).then((x) => {
1452+
fail('Should not succeed');
1453+
}, (e) => {
1454+
expect(e.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
1455+
done();
1456+
});
1457+
});
13501458
});

src/Controllers/DatabaseController.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ DatabaseController.prototype.collectionExists = function(className) {
9191
return this.adapter.classExists(className);
9292
};
9393

94+
DatabaseController.prototype.purgeCollection = function(className) {
95+
return this.loadSchema()
96+
.then((schema) => {
97+
schema.getOneSchema(className)
98+
})
99+
.then((schema) => {
100+
this.adapter.deleteObjectsByQuery(className, {}, schema);
101+
});
102+
};
103+
94104
DatabaseController.prototype.validateClassName = function(className) {
95105
if (this.skipValidation) {
96106
return Promise.resolve();

src/ParseServer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import { SchemasRouter } from './Routers/SchemasRouter';
5050
import { SessionsRouter } from './Routers/SessionsRouter';
5151
import { UserController } from './Controllers/UserController';
5252
import { UsersRouter } from './Routers/UsersRouter';
53+
import { PurgeRouter } from './Routers/PurgeRouter';
5354

5455
import DatabaseController from './Controllers/DatabaseController';
5556
const SchemaController = require('./Controllers/SchemaController');
@@ -291,6 +292,7 @@ class ParseServer {
291292
new IAPValidationRouter(),
292293
new FeaturesRouter(),
293294
new GlobalConfigRouter(),
295+
new PurgeRouter(),
294296
];
295297

296298
if (process.env.PARSE_EXPERIMENTAL_HOOKS_ENABLED || process.env.TESTING) {

src/Routers/FeaturesRouter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class FeaturesRouter extends PromiseRouter {
3636
removeField: true,
3737
addClass: true,
3838
removeClass: true,
39-
clearAllDataFromClass: false,
39+
clearAllDataFromClass: true,
4040
exportClass: false,
4141
editClassLevelPermissions: true,
4242
editPointerPermissions: true,

src/Routers/PurgeRouter.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import PromiseRouter from '../PromiseRouter';
2+
import * as middleware from '../middlewares';
3+
4+
export class PurgeRouter extends PromiseRouter {
5+
6+
handlePurge(req) {
7+
return req.config.database.purgeCollection(req.params.className)
8+
.then(() => {
9+
var cacheAdapter = req.config.cacheController;
10+
if (req.params.className == '_Session') {
11+
cacheAdapter.user.clear();
12+
} else if (req.params.className == '_Role') {
13+
cacheAdapter.role.clear();
14+
}
15+
return {response: {}};
16+
});
17+
}
18+
19+
mountRoutes() {
20+
this.route('DELETE', '/purge/:className', middleware.promiseEnforceMasterKeyAccess, (req) => { return this.handlePurge(req); });
21+
}
22+
}
23+
24+
export default PurgeRouter;

0 commit comments

Comments
 (0)