Skip to content

Commit 39fb0d0

Browse files
committed
Merge pull request #221 from drew-gross/master
Implment GET /parse/schemas
2 parents c866afd + 33a8817 commit 39fb0d0

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed

index.js

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function ParseServer(args) {
111111
router.merge(require('./push'));
112112
router.merge(require('./installations'));
113113
router.merge(require('./functions'));
114+
router.merge(require('./schemas'));
114115

115116
batch.mountOnto(router);
116117

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
},
2424
"devDependencies": {
2525
"codecov": "^1.0.1",
26+
"deep-diff": "^0.3.3",
2627
"istanbul": "^0.4.2",
2728
"jasmine": "^2.3.2",
2829
"mongodb-runner": "^3.1.15"

schemas.js

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// schemas.js
2+
3+
var express = require('express'),
4+
PromiseRouter = require('./PromiseRouter');
5+
6+
var router = new PromiseRouter();
7+
8+
function mongoFieldTypeToApiResponseType(type) {
9+
if (type[0] === '*') {
10+
return {
11+
type: 'Pointer',
12+
targetClass: type.slice(1),
13+
};
14+
}
15+
if (type.startsWith('relation<')) {
16+
return {
17+
type: 'Relation',
18+
targetClass: type.slice('relation<'.length, type.length - 1),
19+
};
20+
}
21+
switch (type) {
22+
case 'number': return {type: 'Number'};
23+
case 'string': return {type: 'String'};
24+
case 'boolean': return {type: 'Boolean'};
25+
case 'date': return {type: 'Date'};
26+
case 'object': return {type: 'Object'};
27+
case 'array': return {type: 'Array'};
28+
case 'geopoint': return {type: 'GeoPoint'};
29+
case 'file': return {type: 'File'};
30+
}
31+
}
32+
33+
function mongoSchemaAPIResponseFields(schema) {
34+
fieldNames = Object.keys(schema).filter(key => key !== '_id');
35+
response = {};
36+
fieldNames.forEach(fieldName => {
37+
response[fieldName] = mongoFieldTypeToApiResponseType(schema[fieldName]);
38+
});
39+
response.ACL = {type: 'ACL'};
40+
response.createdAt = {type: 'Date'};
41+
response.updatedAt = {type: 'Date'};
42+
response.objectId = {type: 'String'};
43+
return response;
44+
}
45+
46+
function mongoSchemaToSchemaAPIResponse(schema) {
47+
return {
48+
className: schema._id,
49+
fields: mongoSchemaAPIResponseFields(schema),
50+
};
51+
}
52+
53+
function getAllSchemas(req) {
54+
if (!req.auth.isMaster) {
55+
return Promise.resolve({
56+
status: 401,
57+
response: {error: 'unauthorized'},
58+
});
59+
}
60+
return req.config.database.collection('_SCHEMA')
61+
.then(coll => coll.find({}).toArray())
62+
.then(schemas => ({response: {
63+
results: schemas.map(mongoSchemaToSchemaAPIResponse)
64+
}}));
65+
}
66+
67+
router.route('GET', '/schemas', getAllSchemas);
68+
69+
module.exports = router;

spec/schemas.spec.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
var request = require('request');
2+
var dd = require('deep-diff');
3+
4+
describe('schemas', () => {
5+
it('requires the master key to get all schemas', (done) => {
6+
request.get({
7+
url: 'http://localhost:8378/1/schemas',
8+
json: true,
9+
headers: {
10+
'X-Parse-Application-Id': 'test',
11+
'X-Parse-REST-API-Key': 'rest',
12+
},
13+
}, (error, response, body) => {
14+
expect(response.statusCode).toEqual(401);
15+
expect(body.error).toEqual('unauthorized');
16+
done();
17+
});
18+
});
19+
20+
it('responds with empty list when there are no schemas', done => {
21+
request.get({
22+
url: 'http://localhost:8378/1/schemas',
23+
json: true,
24+
headers: {
25+
'X-Parse-Application-Id': 'test',
26+
'X-Parse-Master-Key': 'test',
27+
},
28+
}, (error, response, body) => {
29+
expect(body.results).toEqual([]);
30+
done();
31+
});
32+
});
33+
34+
it('responds with a list of schemas after creating objects', done => {
35+
var obj1 = new Parse.Object('HasAllPOD');
36+
obj1.set('aNumber', 5);
37+
obj1.set('aString', 'string');
38+
obj1.set('aBool', true);
39+
obj1.set('aDate', new Date());
40+
obj1.set('aObject', {k1: 'value', k2: true, k3: 5});
41+
obj1.set('aArray', ['contents', true, 5]);
42+
obj1.set('aGeoPoint', new Parse.GeoPoint({latitude: 0, longitude: 0}));
43+
obj1.set('aFile', new Parse.File('f.txt', { base64: 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=' }));
44+
var obj1ACL = new Parse.ACL();
45+
obj1ACL.setPublicWriteAccess(false);
46+
obj1.setACL(obj1ACL);
47+
48+
obj1.save().then(savedObj1 => {
49+
var obj2 = new Parse.Object('HasPointersAndRelations');
50+
obj2.set('aPointer', savedObj1);
51+
var relation = obj2.relation('aRelation');
52+
relation.add(obj1);
53+
return obj2.save();
54+
}).then(() => {
55+
request.get({
56+
url: 'http://localhost:8378/1/schemas',
57+
json: true,
58+
headers: {
59+
'X-Parse-Application-Id': 'test',
60+
'X-Parse-Master-Key': 'test',
61+
},
62+
}, (error, response, body) => {
63+
var expected = {
64+
results: [
65+
{
66+
className: 'HasAllPOD',
67+
fields: {
68+
//Default fields
69+
ACL: {type: 'ACL'},
70+
createdAt: {type: 'Date'},
71+
updatedAt: {type: 'Date'},
72+
objectId: {type: 'String'},
73+
//Custom fields
74+
aNumber: {type: 'Number'},
75+
aString: {type: 'String'},
76+
aBool: {type: 'Boolean'},
77+
aDate: {type: 'Date'},
78+
aObject: {type: 'Object'},
79+
aArray: {type: 'Array'},
80+
aGeoPoint: {type: 'GeoPoint'},
81+
aFile: {type: 'File'}
82+
},
83+
},
84+
{
85+
className: 'HasPointersAndRelations',
86+
fields: {
87+
//Default fields
88+
ACL: {type: 'ACL'},
89+
createdAt: {type: 'Date'},
90+
updatedAt: {type: 'Date'},
91+
objectId: {type: 'String'},
92+
//Custom fields
93+
aPointer: {
94+
type: 'Pointer',
95+
targetClass: 'HasAllPOD',
96+
},
97+
aRelation: {
98+
type: 'Relation',
99+
targetClass: 'HasAllPOD',
100+
},
101+
},
102+
}
103+
]
104+
};
105+
expect(body).toEqual(expected);
106+
done();
107+
})
108+
});
109+
});
110+
});

0 commit comments

Comments
 (0)