Skip to content
This repository was archived by the owner on Dec 26, 2023. It is now read-only.

Commit 9603f4f

Browse files
committed
Initial commit
0 parents  commit 9603f4f

18 files changed

+2215
-0
lines changed

.babelrc

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"presets": [
3+
["env", {
4+
"loose": true
5+
}],
6+
"stage-3"
7+
],
8+
"plugins": ["add-module-exports"]
9+
}

.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# editorconfig.org
2+
root = true
3+
4+
[*]
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
charset = utf-8
9+
trim_trailing_whitespace = true
10+
insert_final_newline = true
11+
12+
[*.md]
13+
trim_trailing_whitespace = false

.gitignore

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
.idea
2+
lib
3+
4+
# Created by https://www.gitignore.io/api/node,linux,macos
5+
6+
### Linux ###
7+
*~
8+
9+
# temporary files which can be created if a process still has a handle open of a deleted file
10+
.fuse_hidden*
11+
12+
# KDE directory preferences
13+
.directory
14+
15+
# Linux trash folder which might appear on any partition or disk
16+
.Trash-*
17+
18+
# .nfs files are created when an open file is removed but is still being accessed
19+
.nfs*
20+
21+
### macOS ###
22+
*.DS_Store
23+
.AppleDouble
24+
.LSOverride
25+
26+
# Icon must end with two \r
27+
Icon
28+
29+
# Thumbnails
30+
._*
31+
32+
# Files that might appear in the root of a volume
33+
.DocumentRevisions-V100
34+
.fseventsd
35+
.Spotlight-V100
36+
.TemporaryItems
37+
.Trashes
38+
.VolumeIcon.icns
39+
.com.apple.timemachine.donotpresent
40+
41+
# Directories potentially created on remote AFP share
42+
.AppleDB
43+
.AppleDesktop
44+
Network Trash Folder
45+
Temporary Items
46+
.apdisk
47+
48+
### Node ###
49+
# Logs
50+
logs
51+
*.log
52+
npm-debug.log*
53+
yarn-debug.log*
54+
yarn-error.log*
55+
56+
# Runtime data
57+
pids
58+
*.pid
59+
*.seed
60+
*.pid.lock
61+
62+
# Directory for instrumented libs generated by jscoverage/JSCover
63+
lib-cov
64+
65+
# Coverage directory used by tools like istanbul
66+
coverage
67+
68+
# nyc test coverage
69+
.nyc_output
70+
71+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
72+
.grunt
73+
74+
# Bower dependency directory (https://bower.io/)
75+
bower_components
76+
77+
# node-waf configuration
78+
.lock-wscript
79+
80+
# Compiled binary addons (http://nodejs.org/api/addons.html)
81+
build/Release
82+
83+
# Dependency directories
84+
node_modules/
85+
jspm_packages/
86+
87+
# Typescript v1 declaration files
88+
typings/
89+
90+
# Optional npm cache directory
91+
.npm
92+
93+
# Optional eslint cache
94+
.eslintcache
95+
96+
# Optional REPL history
97+
.node_repl_history
98+
99+
# Output of 'npm pack'
100+
*.tgz
101+
102+
# Yarn Integrity file
103+
.yarn-integrity
104+
105+
# dotenv environment variables file
106+
.env
107+
108+
109+
110+
# End of https://www.gitignore.io/api/node,linux,macos

package.json

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "parse-server-graphql",
3+
"version": "0.0.0",
4+
"main": "index.js",
5+
"author": "stephentuso",
6+
"license": "MIT",
7+
"scripts": {
8+
"build": "babel src -d lib"
9+
},
10+
"dependencies": {
11+
"axios": "^0.18.0",
12+
"express-graphql": "^0.6.12",
13+
"graphql": "^0.13.1",
14+
"graphql-list-fields": "^2.0.1",
15+
"graphql-type-json": "^0.2.0",
16+
"lodash": "^4.17.5",
17+
"parse": "^1.11.0"
18+
},
19+
"devDependencies": {
20+
"babel-cli": "^6.26.0",
21+
"babel-plugin-add-module-exports": "^0.2.1",
22+
"babel-preset-env": "^1.6.1",
23+
"babel-preset-stage-3": "^6.24.1"
24+
}
25+
}

src/baseMapping.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { mapValues, constant } from 'lodash/fp';
2+
import File from './types/File';
3+
import Date from './types/Date';
4+
import ACL from './types/ACL';
5+
import {
6+
GraphQLString,
7+
GraphQLBoolean,
8+
GraphQLFloat,
9+
} from 'graphql';
10+
11+
export default mapValues(constant, {
12+
File,
13+
Date,
14+
ACL,
15+
String: GraphQLString,
16+
Boolean: GraphQLBoolean,
17+
Number: GraphQLFloat,
18+
});

src/fetchParseSchema.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import axios from 'axios';
2+
import { get } from 'lodash';
3+
4+
export default async function fetchParseSchema({ serverUrl, appId, masterKey }) {
5+
const response = await axios({
6+
method: 'get',
7+
url: `${serverUrl}/schemas`,
8+
headers: {
9+
'X-Parse-Application-Id': appId,
10+
'X-Parse-Master-Key': masterKey,
11+
},
12+
});
13+
14+
if (response.status !== 200) {
15+
throw new Error('Error retrieving Parse schema');
16+
}
17+
18+
if (!get(response, 'data.results') || !response.data.results.length) {
19+
throw new Error('No Parse classes found');
20+
}
21+
22+
return response.data.results;
23+
}

src/generateSchema.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { mapValues } from 'lodash';
2+
import { GraphQLSchema, GraphQLObjectType } from 'graphql';
3+
import typeForClass from './typeForClass';
4+
import dependencyHelper from './utils/dependencyHelper';
5+
import baseMapping from './baseMapping';
6+
import queryForType from './queryForType'
7+
8+
export default function generateSchema(parseSchema) {
9+
const types = dependencyHelper(
10+
baseMapping,
11+
mapValues(parseSchema, typeForClass),
12+
);
13+
14+
const queryFields = parseSchema.reduce((acc, { className }) => ({
15+
...acc,
16+
[className]: queryForType(types[className]),
17+
}), {});
18+
19+
const query = new GraphQLObjectType({
20+
name: 'Query',
21+
fields: queryFields,
22+
});
23+
24+
return new GraphQLSchema({ query });
25+
}

src/index.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import graphqlHTTP from 'express-graphql';
2+
import generateSchema from './generateSchema';
3+
import fetchParseSchema from './fetchParseSchema';
4+
5+
const getSchema = (() => {
6+
let schema;
7+
return async (options, alwaysRecreate) => {
8+
if (!schema || alwaysRecreate) {
9+
schema = generateSchema(await fetchParseSchema(options));
10+
}
11+
return schema;
12+
}
13+
});
14+
15+
export default function parseGraphQL(options) {
16+
return graphqlHTTP(async () => ({
17+
schema: await getSchema(options, options.dynamicSchema),
18+
graphiql: true,
19+
}));
20+
};

src/mapType.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function mapType({ type, ...params }, mapping) {
2+
return mapping[type](params);
3+
}

src/queryForType.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { GraphQLList } from 'graphql';
2+
import JSON from 'graphql-type-json';
3+
import getFieldNames from 'graphql-list-fields';
4+
import Parse from 'parse/node';
5+
6+
export default (Type) => ({
7+
type: GraphQLList(Type),
8+
args: {
9+
json: {
10+
type: JSON,
11+
}
12+
},
13+
resolve(_, { json }, context, info) {
14+
const fields = getFieldNames(info);
15+
const query = Parse.Query.from(json);
16+
fields.forEach(field => query.include(field));
17+
return query.find();
18+
}
19+
});

src/typeForClass.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { GraphQLObjectType } from 'graphql';
2+
import JSON from 'graphql-type-json';
3+
import { mapValues } from 'lodash';
4+
import { property } from 'lodash/fp';
5+
import mapType from './mapType';
6+
7+
const specialResolvers = {
8+
objectId: property('id'),
9+
ACL: item => item.getACL(),
10+
createdAt: property('createdAt'),
11+
updatedAt: property('updatedAt'),
12+
};
13+
14+
const standardResolver = key => item => item.get(key);
15+
16+
export default ({ className, fields }) => (typeMap) => () => {
17+
const propertyFields = () => mapValues(fields, (data, key) => ({
18+
type: mapType(data, typeMap),
19+
resolve: specialResolvers[key] || standardResolver(key),
20+
}));
21+
22+
return new GraphQLObjectType({
23+
name: className,
24+
fields: () => ({
25+
...propertyFields(),
26+
parseObjectJSON: {
27+
type: JSON,
28+
resolve: item => item.toJSON(),
29+
},
30+
}),
31+
});
32+
};

src/types/ACL.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { GraphQLObjectType, GraphQLBoolean } from 'graphql';
2+
3+
export default new GraphQLObjectType({
4+
name: 'ACL',
5+
fields: {
6+
publicReadAccess: {
7+
type: GraphQLBoolean,
8+
resolve(acl) {
9+
return acl.getPublicReadAccess();
10+
},
11+
},
12+
publicWriteAccess: {
13+
type: GraphQLBoolean,
14+
resolve(acl) {
15+
return acl.getPublicWriteAccess();
16+
},
17+
},
18+
readAccess: {
19+
type: GraphQLBoolean,
20+
resolve(acl, { userId }) {
21+
return acl.getReadAccess(userId);
22+
},
23+
},
24+
writeAccess: {
25+
type: GraphQLBoolean,
26+
resolve(acl, { userId }) {
27+
return acl.getWriteAccess(userId);
28+
},
29+
},
30+
roleReadAccess: {
31+
type: GraphQLBoolean,
32+
resolve(acl, { role }) {
33+
return acl.getRoleReadAccess(role);
34+
},
35+
},
36+
roleWriteAccess: {
37+
type: GraphQLBoolean,
38+
resolve(acl, { role }) {
39+
return acl.getRoleWriteAccess(role);
40+
},
41+
},
42+
},
43+
});

src/types/Date.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { GraphQLScalarType } from 'graphql';
2+
import { Kind } from 'graphql/language';
3+
import moment from 'moment';
4+
import { identity } from 'lodash';
5+
6+
const parseValue = dateString => new Date(dateString);
7+
8+
function parseLiteral(ast) {
9+
if (ast.kind !== Kind.STRING) {
10+
throw new Error('Invalid value for Date scalar');
11+
}
12+
13+
return parseValue(ast.value);
14+
}
15+
16+
export default new GraphQLScalarType({
17+
name: 'Date',
18+
serialize: date => date.toISOString(),
19+
parseValue,
20+
parseLiteral,
21+
});

src/types/File.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {
2+
GraphQLObjectType,
3+
GraphQLString,
4+
} from 'graphql';
5+
6+
export default new GraphQLObjectType({
7+
name: 'File',
8+
fields: {
9+
url: {
10+
type: GraphQLString,
11+
resolve(file) {
12+
return file.url();
13+
},
14+
},
15+
name: {
16+
type: GraphQLString,
17+
resolve(file) {
18+
return file.url();
19+
},
20+
},
21+
},
22+
});

0 commit comments

Comments
 (0)