Skip to content

Commit cfb335e

Browse files
committed
Endpoints for audiences CRUD
1 parent 16954c2 commit cfb335e

File tree

6 files changed

+283
-6
lines changed

6 files changed

+283
-6
lines changed

spec/AudienceRouter.spec.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
var auth = require('../src/Auth');
2+
var Config = require('../src/Config');
3+
var rest = require('../src/rest');
4+
var AudiencesRouter = require('../src/Routers/AudiencesRouter').AudiencesRouter;
5+
6+
describe('AudiencesRouter', () => {
7+
it('uses find condition from request.body', (done) => {
8+
var config = new Config('test');
9+
var androidAudienceRequest = {
10+
'name': 'Android Users',
11+
'query': '{ "test": "android" }'
12+
};
13+
var iosAudienceRequest = {
14+
'name': 'Iphone Users',
15+
'query': '{ "test": "ios" }'
16+
};
17+
var request = {
18+
config: config,
19+
auth: auth.master(config),
20+
body: {
21+
where: {
22+
query: '{ "test": "android" }'
23+
}
24+
},
25+
query: {},
26+
info: {}
27+
};
28+
29+
var router = new AudiencesRouter();
30+
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
31+
.then(() => {
32+
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
33+
}).then(() => {
34+
return router.handleFind(request);
35+
}).then((res) => {
36+
var results = res.response.results;
37+
expect(results.length).toEqual(1);
38+
done();
39+
}).catch((err) => {
40+
fail(JSON.stringify(err));
41+
done();
42+
});
43+
});
44+
45+
it('uses find condition from request.query', (done) => {
46+
var config = new Config('test');
47+
var androidAudienceRequest = {
48+
'name': 'Android Users',
49+
'query': '{ "test": "android" }'
50+
};
51+
var iosAudienceRequest = {
52+
'name': 'Iphone Users',
53+
'query': '{ "test": "ios" }'
54+
};
55+
var request = {
56+
config: config,
57+
auth: auth.master(config),
58+
body: {},
59+
query: {
60+
where: {
61+
'query': '{ "test": "android" }'
62+
}
63+
},
64+
info: {}
65+
};
66+
67+
var router = new AudiencesRouter();
68+
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
69+
.then(() => {
70+
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
71+
}).then(() => {
72+
return router.handleFind(request);
73+
}).then((res) => {
74+
var results = res.response.results;
75+
expect(results.length).toEqual(1);
76+
done();
77+
}).catch((err) => {
78+
fail(err);
79+
done();
80+
});
81+
});
82+
83+
it('query installations with limit = 0', (done) => {
84+
var config = new Config('test');
85+
var androidAudienceRequest = {
86+
'name': 'Android Users',
87+
'query': '{ "test": "android" }'
88+
};
89+
var iosAudienceRequest = {
90+
'name': 'Iphone Users',
91+
'query': '{ "test": "ios" }'
92+
};
93+
var request = {
94+
config: config,
95+
auth: auth.master(config),
96+
body: {},
97+
query: {
98+
limit: 0
99+
},
100+
info: {}
101+
};
102+
103+
new Config('test');
104+
var router = new AudiencesRouter();
105+
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
106+
.then(() => {
107+
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
108+
}).then(() => {
109+
return router.handleFind(request);
110+
}).then((res) => {
111+
var response = res.response;
112+
expect(response.results.length).toEqual(0);
113+
done();
114+
}).catch((err) => {
115+
fail(JSON.stringify(err));
116+
done();
117+
});
118+
});
119+
120+
it('query installations with count = 1', done => {
121+
var config = new Config('test');
122+
var androidAudienceRequest = {
123+
'name': 'Android Users',
124+
'query': '{ "test": "android" }'
125+
};
126+
var iosAudienceRequest = {
127+
'name': 'Iphone Users',
128+
'query': '{ "test": "ios" }'
129+
};
130+
var request = {
131+
config: config,
132+
auth: auth.master(config),
133+
body: {},
134+
query: {
135+
count: 1
136+
},
137+
info: {}
138+
};
139+
140+
var router = new AudiencesRouter();
141+
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
142+
.then(() => rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest))
143+
.then(() => router.handleFind(request))
144+
.then((res) => {
145+
var response = res.response;
146+
expect(response.results.length).toEqual(2);
147+
expect(response.count).toEqual(2);
148+
done();
149+
})
150+
.catch(error => {
151+
fail(JSON.stringify(error));
152+
done();
153+
})
154+
});
155+
156+
it('query installations with limit = 0 and count = 1', (done) => {
157+
var config = new Config('test');
158+
var androidAudienceRequest = {
159+
'name': 'Android Users',
160+
'query': '{ "test": "android" }'
161+
};
162+
var iosAudienceRequest = {
163+
'name': 'Iphone Users',
164+
'query': '{ "test": "ios" }'
165+
};
166+
var request = {
167+
config: config,
168+
auth: auth.master(config),
169+
body: {},
170+
query: {
171+
limit: 0,
172+
count: 1
173+
},
174+
info: {}
175+
};
176+
177+
var router = new AudiencesRouter();
178+
rest.create(config, auth.nobody(config), '_Audience', androidAudienceRequest)
179+
.then(() => {
180+
return rest.create(config, auth.nobody(config), '_Audience', iosAudienceRequest);
181+
}).then(() => {
182+
return router.handleFind(request);
183+
}).then((res) => {
184+
var response = res.response;
185+
expect(response.results.length).toEqual(0);
186+
expect(response.count).toEqual(2);
187+
done();
188+
}).catch((err) => {
189+
fail(JSON.stringify(err));
190+
done();
191+
});
192+
});
193+
});

src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ export class PostgresStorageAdapter {
667667
const joins = results.reduce((list, schema) => {
668668
return list.concat(joinTablesForSchema(schema.schema));
669669
}, []);
670-
const classes = ['_SCHEMA','_PushStatus','_JobStatus','_JobSchedule','_Hooks','_GlobalConfig', ...results.map(result => result.className), ...joins];
670+
const classes = ['_SCHEMA','_PushStatus','_JobStatus','_JobSchedule','_Hooks','_GlobalConfig','_Audience', ...results.map(result => result.className), ...joins];
671671
return this._client.tx(t=>t.batch(classes.map(className=>t.none('DROP TABLE IF EXISTS $<className:name>', { className }))));
672672
}, error => {
673673
if (error.code === PostgresRelationDoesNotExistError) {

src/Controllers/SchemaController.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ const defaultColumns = Object.freeze({
114114
_GlobalConfig: {
115115
"objectId": {type: 'String'},
116116
"params": {type: 'Object'}
117+
},
118+
_Audience: {
119+
"objectId": {type:'String'},
120+
"name": {type:'String'},
121+
"query": {type:'String'} //storing query as JSON string to prevent "Nested keys should not contain the '$' or '.' characters" error
117122
}
118123
});
119124

@@ -122,9 +127,9 @@ const requiredColumns = Object.freeze({
122127
_Role: ["name", "ACL"]
123128
});
124129

125-
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus', '_JobStatus', '_JobSchedule']);
130+
const systemClasses = Object.freeze(['_User', '_Installation', '_Role', '_Session', '_Product', '_PushStatus', '_JobStatus', '_JobSchedule', '_Audience']);
126131

127-
const volatileClasses = Object.freeze(['_JobStatus', '_PushStatus', '_Hooks', '_GlobalConfig', '_JobSchedule']);
132+
const volatileClasses = Object.freeze(['_JobStatus', '_PushStatus', '_Hooks', '_GlobalConfig', '_JobSchedule', '_Audience']);
128133

129134
// 10 alpha numberic chars + uppercase
130135
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
@@ -306,7 +311,11 @@ const _JobScheduleSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
306311
fields: {},
307312
classLevelPermissions: {}
308313
}));
309-
const VolatileClassesSchemas = [_HooksSchema, _JobStatusSchema, _JobScheduleSchema, _PushStatusSchema, _GlobalConfigSchema];
314+
const _AudienceSchema = convertSchemaToAdapterSchema(injectDefaultSchema({
315+
className: "_Audience",
316+
fields: defaultColumns._Audience
317+
}));
318+
const VolatileClassesSchemas = [_HooksSchema, _JobStatusSchema, _JobScheduleSchema, _PushStatusSchema, _GlobalConfigSchema, _AudienceSchema];
310319

311320
const dbTypeMatchesObjectType = (dbType, objectType) => {
312321
if (dbType.type !== objectType.type) return false;

src/ParseServer.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { SessionsRouter } from './Routers/SessionsRouter';
4949
import { UserController } from './Controllers/UserController';
5050
import { UsersRouter } from './Routers/UsersRouter';
5151
import { PurgeRouter } from './Routers/PurgeRouter';
52+
import { AudiencesRouter } from './Routers/AudiencesRouter';
5253

5354
import DatabaseController from './Controllers/DatabaseController';
5455
import SchemaCache from './Controllers/SchemaCache';
@@ -379,7 +380,8 @@ class ParseServer {
379380
new GlobalConfigRouter(),
380381
new PurgeRouter(),
381382
new HooksRouter(),
382-
new CloudCodeRouter()
383+
new CloudCodeRouter(),
384+
new AudiencesRouter()
383385
];
384386

385387
const routes = routers.reduce((memo, router) => {

src/Routers/AudiencesRouter.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import ClassesRouter from './ClassesRouter';
2+
import rest from '../rest';
3+
import * as middleware from '../middlewares';
4+
5+
export class AudiencesRouter extends ClassesRouter {
6+
handleFind(req) {
7+
const body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
8+
var options = {};
9+
10+
if (body.skip) {
11+
options.skip = Number(body.skip);
12+
}
13+
if (body.limit || body.limit === 0) {
14+
options.limit = Number(body.limit);
15+
}
16+
if (body.order) {
17+
options.order = String(body.order);
18+
}
19+
if (body.count) {
20+
options.count = true;
21+
}
22+
if (body.include) {
23+
options.include = String(body.include);
24+
}
25+
26+
return rest.find(req.config, req.auth, '_Audience', body.where, options, req.info.clientSDK)
27+
.then((response) => {
28+
29+
response.results.forEach((item) => {
30+
item.query = JSON.parse(item.query);
31+
});
32+
33+
return {response: response};
34+
});
35+
}
36+
37+
handleGet(req) {
38+
req.params.className = '_Audience';
39+
return super.handleGet(req)
40+
.then((data) => {
41+
data.response.results.forEach((item) => {
42+
item.query = JSON.parse(item.query);
43+
});
44+
45+
return data;
46+
});
47+
}
48+
49+
handleCreate(req) {
50+
req.params.className = '_Audience';
51+
return super.handleCreate(req);
52+
}
53+
54+
handleUpdate(req) {
55+
req.params.className = '_Audience';
56+
return super.handleUpdate(req);
57+
}
58+
59+
handleDelete(req) {
60+
req.params.className = '_Audience';
61+
return super.handleDelete(req);
62+
}
63+
64+
mountRoutes() {
65+
this.route('GET','/push_audiences', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleFind(req); });
66+
this.route('GET','/push_audiences/:objectId', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleGet(req); });
67+
this.route('POST','/push_audiences', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleCreate(req); });
68+
this.route('PUT','/push_audiences/:objectId', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleUpdate(req); });
69+
this.route('DELETE','/push_audiences/:objectId', middleware.promiseEnforceMasterKeyAccess, req => { return this.handleDelete(req); });
70+
}
71+
}
72+
73+
export default AudiencesRouter;

src/Routers/FeaturesRouter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class FeaturesRouter extends PromiseRouter {
3232
immediatePush: req.config.hasPushSupport,
3333
scheduledPush: req.config.hasPushScheduledSupport,
3434
storedPushData: req.config.hasPushSupport,
35-
pushAudiences: false,
35+
pushAudiences: true,
3636
},
3737
schemas: {
3838
addField: true,

0 commit comments

Comments
 (0)