Skip to content

Commit 5ed6435

Browse files
committed
Endpoint for importing json data in a class
1 parent c32ed52 commit 5ed6435

File tree

7 files changed

+356
-4
lines changed

7 files changed

+356
-4
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"babel-polyfill": "6.8.0",
2222
"babel-runtime": "6.6.1",
2323
"bcrypt-nodejs": "0.0.3",
24+
"bluebird": "^3.4.6",
2425
"body-parser": "1.15.2",
2526
"colors": "1.1.2",
2627
"commander": "2.9.0",

spec/ParseAPI.spec.js

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,4 +1462,234 @@ describe('miscellaneous', function() {
14621462
done();
14631463
});
14641464
});
1465+
1466+
it_exclude_dbs(['postgres'])('import objects from rest array', (done) => {
1467+
let headers = {
1468+
'Content-Type': 'application/json',
1469+
'X-Parse-Application-Id': 'test',
1470+
'X-Parse-Master-Key': 'test'
1471+
};
1472+
request.post(
1473+
{
1474+
headers: headers,
1475+
url: 'http://localhost:8378/1/import/TestObject',
1476+
body: JSON.stringify([
1477+
{ column1: 'row1Column1', column2: 'row1Column2' },
1478+
{ column1: 'row2Column1', column2: 'row2Column2' }
1479+
])
1480+
},
1481+
(err) => {
1482+
expect(err).toBe(null);
1483+
let query = new Parse.Query('TestObject');
1484+
query.ascending('column1');
1485+
query.find().then((results) => {
1486+
expect(results.length).toEqual(2);
1487+
expect(results[0].get('column1')).toEqual('row1Column1');
1488+
expect(results[0].get('column2')).toEqual('row1Column2');
1489+
expect(results[1].get('column1')).toEqual('row2Column1');
1490+
expect(results[1].get('column2')).toEqual('row2Column2');
1491+
done();
1492+
});
1493+
}
1494+
);
1495+
});
1496+
1497+
it_exclude_dbs(['postgres'])('import objects from json with results field', (done) => {
1498+
let headers = {
1499+
'Content-Type': 'application/json',
1500+
'X-Parse-Application-Id': 'test',
1501+
'X-Parse-Master-Key': 'test'
1502+
};
1503+
request.post(
1504+
{
1505+
headers: headers,
1506+
url: 'http://localhost:8378/1/import/TestObject',
1507+
body: JSON.stringify({
1508+
results: [
1509+
{ column1: 'row1Column1', column2: 'row1Column2' },
1510+
{ column1: 'row2Column1', column2: 'row2Column2' }
1511+
]
1512+
})
1513+
},
1514+
(err) => {
1515+
expect(err).toBe(null);
1516+
let query = new Parse.Query('TestObject');
1517+
query.ascending('column1');
1518+
query.find().then((results) => {
1519+
expect(results.length).toEqual(2);
1520+
expect(results[0].get('column1')).toEqual('row1Column1');
1521+
expect(results[0].get('column2')).toEqual('row1Column2');
1522+
expect(results[1].get('column1')).toEqual('row2Column1');
1523+
expect(results[1].get('column2')).toEqual('row2Column2');
1524+
done();
1525+
});
1526+
}
1527+
);
1528+
});
1529+
1530+
it_exclude_dbs(['postgres'])('import objects with object id', (done) => {
1531+
let headers = {
1532+
'Content-Type': 'application/json',
1533+
'X-Parse-Application-Id': 'test',
1534+
'X-Parse-Master-Key': 'test'
1535+
};
1536+
request.post(
1537+
{
1538+
headers: headers,
1539+
url: 'http://localhost:8378/1/import/TestObject',
1540+
body: JSON.stringify({
1541+
results: [
1542+
{
1543+
"objectId": "aaaaaaaaaa",
1544+
"data": "somedataa"
1545+
},
1546+
{
1547+
"objectId": "bbbbbbbbbb",
1548+
"data": "somedatab"
1549+
}
1550+
]
1551+
})
1552+
},
1553+
(err) => {
1554+
expect(err).toBe(null);
1555+
let query = new Parse.Query('TestObject');
1556+
query.ascending('data');
1557+
query.find().then((results) => {
1558+
expect(results.length).toEqual(2);
1559+
expect(results[0].id).toEqual('aaaaaaaaaa');
1560+
expect(results[1].id).toEqual('bbbbbbbbbb');
1561+
done();
1562+
});
1563+
}
1564+
);
1565+
});
1566+
1567+
it_exclude_dbs(['postgres'])('update objects with existing object id', (done) => {
1568+
let headers = {
1569+
'Content-Type': 'application/json',
1570+
'X-Parse-Application-Id': 'test',
1571+
'X-Parse-Master-Key': 'test'
1572+
};
1573+
request.post(
1574+
{
1575+
headers: headers,
1576+
url: 'http://localhost:8378/1/import/TestObject',
1577+
body: JSON.stringify({
1578+
results: [
1579+
{
1580+
"objectId": "aaaaaaaaaa",
1581+
"data": "somedataa"
1582+
},
1583+
{
1584+
"objectId": "bbbbbbbbbb",
1585+
"data": "somedatab"
1586+
}
1587+
]
1588+
})
1589+
},
1590+
(err) => {
1591+
expect(err).toBe(null);
1592+
request.post(
1593+
{
1594+
headers: headers,
1595+
url: 'http://localhost:8378/1/import/TestObject',
1596+
body: JSON.stringify({
1597+
results: [
1598+
{
1599+
"objectId": "aaaaaaaaaa",
1600+
"data": "somedataa2"
1601+
}
1602+
]
1603+
})
1604+
},
1605+
(err) => {
1606+
expect(err).toBe(null);
1607+
let query = new Parse.Query('TestObject');
1608+
query.ascending('data');
1609+
query.find().then((results) => {
1610+
expect(results.length).toEqual(2);
1611+
expect(results[0].id).toEqual('aaaaaaaaaa');
1612+
expect(results[0].get('data')).toEqual('somedataa2');
1613+
expect(results[1].id).toEqual('bbbbbbbbbb');
1614+
expect(results[1].get('data')).toEqual('somedatab');
1615+
done();
1616+
});
1617+
}
1618+
);
1619+
}
1620+
);
1621+
});
1622+
1623+
it_exclude_dbs(['postgres'])('import relations object from json', (done) => {
1624+
let headers = {
1625+
'Content-Type': 'application/json',
1626+
'X-Parse-Application-Id': 'test',
1627+
'X-Parse-Master-Key': 'test'
1628+
};
1629+
1630+
let promises = [];
1631+
1632+
let object = new Parse.Object('TestObjectDad');
1633+
let relatedObject = new Parse.Object('TestObjectChild');
1634+
Parse.Object.saveAll([object, relatedObject]).then(() => {
1635+
object.relation('RelationObject').add(relatedObject);
1636+
return object.save();
1637+
});
1638+
1639+
promises.push(rp({
1640+
method: 'POST',
1641+
headers: headers,
1642+
url: 'http://localhost:8378/1/import/TestObjectDad',
1643+
body: JSON.stringify({
1644+
results: [
1645+
{
1646+
"objectId": "aaa",
1647+
"Name": "namea",
1648+
}
1649+
]
1650+
})
1651+
}));
1652+
1653+
promises.push(rp({
1654+
method: 'POST',
1655+
headers: headers,
1656+
url: 'http://localhost:8378/1/import/TestObjectChild',
1657+
body: JSON.stringify({
1658+
results: [
1659+
{
1660+
"objectId": "bbb",
1661+
"Name": "nameb"
1662+
}
1663+
]
1664+
})
1665+
}));
1666+
1667+
Promise.all(promises).then(() => {
1668+
rp(
1669+
{
1670+
method: 'POST',
1671+
headers: headers,
1672+
url: 'http://localhost:8378/1/import/TestObjectDad/RelationObject',
1673+
body: JSON.stringify({
1674+
results: [
1675+
{
1676+
"owningId": "aaa",
1677+
"relatedId": "bbb"
1678+
}
1679+
]
1680+
})
1681+
},
1682+
(err) => {
1683+
expect(err).toBe(null);
1684+
let query = new Parse.Query('TestObjectDad');
1685+
query._where = {"RelationObject":{"__type":"Pointer", "className":"TestObjectChild", "objectId":"bbb"}};
1686+
query.find().then((results) => {
1687+
expect(results.length).toEqual(1);
1688+
expect(results[0].id).toEqual('aaa');
1689+
done();
1690+
});
1691+
}
1692+
)
1693+
});
1694+
});
14651695
});

src/ParseServer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import { SessionsRouter } from './Routers/SessionsRouter';
5151
import { UserController } from './Controllers/UserController';
5252
import { UsersRouter } from './Routers/UsersRouter';
5353
import { PurgeRouter } from './Routers/PurgeRouter';
54+
import { ImportRouter } from './Routers/ImportRouter';
55+
import { ImportRelationRouter } from './Routers/ImportRelationRouter';
5456

5557
import DatabaseController from './Controllers/DatabaseController';
5658
const SchemaController = require('./Controllers/SchemaController');
@@ -300,6 +302,8 @@ class ParseServer {
300302
new FeaturesRouter(),
301303
new GlobalConfigRouter(),
302304
new PurgeRouter(),
305+
new ImportRouter(),
306+
new ImportRelationRouter(),
303307
];
304308

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

src/RestWrite.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ import _ from 'lodash';
2323
// RestWrite will handle objectId, createdAt, and updatedAt for
2424
// everything. It also knows to use triggers and special modifications
2525
// for the _User class.
26-
function RestWrite(config, auth, className, query, data, originalData) {
26+
function RestWrite(config, auth, className, query, data, originalData, clientSDK, options) {
2727
this.config = config;
2828
this.auth = auth;
2929
this.className = className;
3030
this.storage = {};
3131
this.runOptions = {};
3232

33-
if (!query && data.objectId) {
33+
let allowObjectId = options && options.allowObjectId === true;
34+
if (!query && data.objectId && !allowObjectId) {
3435
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId is an invalid field name.');
3536
}
3637

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import PromiseRouter from '../PromiseRouter';
2+
import * as middleware from '../middlewares';
3+
import rest from '../rest';
4+
5+
export class ImportRelationRouter extends PromiseRouter {
6+
handleImportRelation(req) {
7+
8+
function getOneSchema() {
9+
let className = req.params.className;
10+
return req.config.database.loadSchema({clearCache: true})
11+
.then(schemaController => schemaController.getOneSchema(className))
12+
.catch(error => {
13+
if (error === undefined) {
14+
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
15+
} else {
16+
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error.');
17+
}
18+
});
19+
}
20+
21+
return getOneSchema().then((response) => {
22+
23+
if (!response.fields.hasOwnProperty(req.params.relationName)) {
24+
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Relation ${req.params.relationName} does not exist in ${req.params.className}.`);
25+
} else if(response.fields[req.params.relationName].type !== 'Relation') {
26+
throw new Parse.Error(Parse.Error.INVALID_TYPE, `Class ${response.fields[req.params.relationName].targetClass} does not have Relation type.`);
27+
}
28+
29+
let targetClass = response.fields[req.params.relationName].targetClass;
30+
let promises = [];
31+
let restObjects = [];
32+
33+
if (Array.isArray(req.body)) {
34+
restObjects = req.body;
35+
} else if (Array.isArray(req.body.results)) {
36+
restObjects = req.body.results;
37+
}
38+
39+
restObjects.forEach((restObjects) => {
40+
promises.push(
41+
rest.update(req.config, req.auth, req.params.className, restObjects.owningId, {[req.params.relationName]: {"__op": "AddRelation", "objects": [{"__type": "Pointer", "className": targetClass, "objectId": restObjects.relatedId}]}}, req.info.clientSDK)
42+
.catch(function (error) {
43+
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found');
44+
})
45+
)
46+
});
47+
48+
return Promise.all(promises).then((results) => {
49+
return {response: results};
50+
});
51+
});
52+
}
53+
54+
mountRoutes() {
55+
this.route(
56+
'POST',
57+
'/import/:className/:relationName',
58+
middleware.promiseEnforceMasterKeyAccess,
59+
(req) => { return this.handleImportRelation(req); }
60+
);
61+
}
62+
}
63+
64+
export default ImportRelationRouter;

src/Routers/ImportRouter.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import PromiseRouter from '../PromiseRouter';
2+
import * as middleware from '../middlewares';
3+
import rest from '../rest';
4+
import Promise from 'bluebird';
5+
6+
export class ImportRouter extends PromiseRouter {
7+
handleImport(req) {
8+
var restObjects = [];
9+
if (Array.isArray(req.body)) {
10+
restObjects = req.body;
11+
} else if (Array.isArray(req.body.results)) {
12+
restObjects = req.body.results;
13+
}
14+
return Promise
15+
.map(restObjects, importRestObject, { concurrency: 5 })
16+
.then((results) => {
17+
return {response: results};
18+
});
19+
20+
function importRestObject(restObject) {
21+
if (restObject.objectId) {
22+
return rest
23+
.update(req.config, req.auth, req.params.className, restObject.objectId, restObject, req.info.clientSDK)
24+
.catch(function (error) {
25+
if (error.code === Parse.Error.OBJECT_NOT_FOUND) {
26+
return rest.create(
27+
req.config,
28+
req.auth,
29+
req.params.className,
30+
restObject,
31+
req.info.clientSDK,
32+
{allowObjectId: true}
33+
);
34+
}
35+
});
36+
} else {
37+
return rest.create(req.config, req.auth, req.params.className, restObject, req.info.clientSDK);
38+
}
39+
}
40+
}
41+
42+
mountRoutes() {
43+
this.route(
44+
'POST',
45+
'/import/:className',
46+
middleware.promiseEnforceMasterKeyAccess,
47+
(req) => { return this.handleImport(req); }
48+
);
49+
}
50+
}
51+
52+
export default ImportRouter;

0 commit comments

Comments
 (0)