Skip to content
This repository was archived by the owner on Nov 5, 2024. It is now read-only.

Commit 144723b

Browse files
committed
⊂(◕‿◕)つ ADVANCED GRAPHQL
1 parent 6a40b58 commit 144723b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+12728
-0
lines changed

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
insert_final_newline = true

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# OK GROW! Training
2+
3+
Look at the README of the `/api` & `/ui` folders to get started!
4+
5+
We strive to use best practices in this repo, but we prioritize the learning experience where necessary. This usually just means a simplified file structure, but this app lacks some safety and security features, so please use your judgment when reusing this code. If you have any questions or concerns about specific code, please ask us; we love to talk about code quality.

api/.babelrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
presets: ['env'],
3+
plugins: [
4+
'transform-runtime',
5+
'transform-async-generator-functions',
6+
'transform-object-rest-spread',
7+
],
8+
}

api/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
PORT=8080
2+
GOOGLE_API_KEY=
3+
DARKSKY_API_KEY=
4+
ENGINE_API_KEY=

api/.eslintrc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": ["prettier"],
3+
"plugins": ["prettier"],
4+
"rules": {
5+
"prettier/prettier": [
6+
"error",
7+
{
8+
"singleQuote": true,
9+
"trailingComma": "es5",
10+
"printWidth": 80
11+
}
12+
]
13+
}
14+
}

api/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/db
2+
/node_modules
3+
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
8+
.env

api/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# OK GROW! Training - API Server
2+
3+
```sh
4+
# make a .env file from the example
5+
cp .env.example .env
6+
```
7+
8+
Update the .env file with your api keys:
9+
10+
* GOOGLE_API_KEY - use the same value as REACT_APP_GOOGLE_API_KEY in the ui folder: [Google Maps API](https://developers.google.com/maps/documentation/javascript/get-api-key)
11+
* [DARKSKY_API_KEY](https://darksky.net/dev)
12+
* [ENGINE_API_KEY](https://engine.apollographql.com/)
13+
14+
```sh
15+
# install the dependencies
16+
yarn
17+
18+
# run the API server
19+
yarn start
20+
```
21+
22+
If the API server doesn't start, you may need to start MongoDB in a separate terminal window (you will have to make sure [mongo is installed](https://docs.mongodb.com/manual/installation/)):
23+
24+
```sh
25+
# Usually not necessary
26+
mongod
27+
```

api/index.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import dotenv from 'dotenv-safe';
2+
import nodemon from 'nodemon';
3+
import fs from 'fs';
4+
import path from 'path';
5+
import mongoPrebuilt from 'mongodb-prebuilt';
6+
import denodeify from 'denodeify';
7+
8+
const dbpath = `${__dirname}/db`;
9+
10+
dotenv.config();
11+
12+
const {
13+
PORT = 8080,
14+
MONGO_PORT = parseInt(PORT, 10) + 2,
15+
MONGO_URL
16+
} = process.env;
17+
18+
// Taken from https://github.com/meteor/meteor/blob/debug-circle-timeout-promise-await/tools/utils/mongo-exit-codes.js
19+
const MONGO_CODES = {
20+
0: {
21+
code: 0,
22+
symbol: 'EXIT_CLEAN',
23+
longText: 'MongoDB exited cleanly'
24+
},
25+
1: {
26+
code: 1,
27+
// No symbol in the source. This is in src/mongo/base/initializer.cpp.
28+
symbol: 'global-initialization',
29+
longText: 'MongoDB failed global initialization'
30+
},
31+
2: {
32+
code: 2,
33+
symbol: 'EXIT_BADOPTIONS',
34+
longText:
35+
'MongoDB was started with erroneous or incompatible command line options'
36+
},
37+
3: {
38+
code: 3,
39+
symbol: 'EXIT_REPLICATION_ERROR',
40+
longText:
41+
'There was an inconsistency between hostnames specified\n' +
42+
'on the command line compared with hostnames stored in local.sources'
43+
},
44+
4: {
45+
code: 4,
46+
symbol: 'EXIT_NEED_UPGRADE',
47+
longText: 'MongoDB needs to upgrade to use this database'
48+
},
49+
5: {
50+
code: 5,
51+
symbol: 'EXIT_SHARDING_ERROR',
52+
longText: 'A moveChunk operation failed'
53+
},
54+
12: {
55+
code: 12,
56+
symbol: 'EXIT_KILL',
57+
longText: 'The MongoDB process was killed, on Windows'
58+
},
59+
14: {
60+
code: 14,
61+
symbol: 'EXIT_ABRUPT',
62+
longText: 'Unspecified unrecoverable error. Exit was not clean'
63+
},
64+
20: {
65+
code: 20,
66+
symbol: 'EXIT_NTSERVICE_ERROR',
67+
longText: 'Error managing NT Service on Windows'
68+
},
69+
45: {
70+
code: 45,
71+
symbol: 'EXIT_FS',
72+
longText: 'MongoDB cannot open or obtain a lock on a file'
73+
},
74+
47: {
75+
code: 47,
76+
symbol: 'EXIT_CLOCK_SKEW',
77+
longText: 'MongoDB exited due to excess clock skew'
78+
},
79+
48: {
80+
code: 48,
81+
symbol: 'EXIT_NET_ERROR',
82+
longText:
83+
'MongoDB exited because its port was closed, or was already\n' +
84+
'taken by a previous instance of MongoDB'
85+
},
86+
100: {
87+
code: 100,
88+
symbol: 'EXIT_UNCAUGHT',
89+
longText:
90+
'MongoDB had an unspecified uncaught exception.\n' +
91+
'This can be caused by MongoDB being unable to write to a local database.\n' +
92+
`Check that you have permissions to write to ${dbpath}. MongoDB does\n` +
93+
'not support filesystems like NFS that do not allow file locking.'
94+
}
95+
};
96+
97+
if (!MONGO_URL) {
98+
console.log(
99+
`Creating development MongoDB on mongodb://localhost:${MONGO_PORT}`
100+
);
101+
102+
if (!fs.existsSync(dbpath)) {
103+
fs.mkdirSync(dbpath);
104+
}
105+
106+
// Weirdly, this promise never resolves if Mongo starts.
107+
// However, we'll just go ahead and start the node server anyway,
108+
// and if we see an error, we'll quit
109+
denodeify(mongoPrebuilt.start_server.bind(mongoPrebuilt))({
110+
auto_shutdown: true,
111+
args: {
112+
port: MONGO_PORT,
113+
dbpath
114+
}
115+
}).catch(errorCode => {
116+
const error = MONGO_CODES[errorCode];
117+
console.error(`Failed to start MongoDB server on port ${MONGO_PORT}`);
118+
console.error(
119+
`Error Code ${errorCode}: ${error ? error.longText : 'Unknown'}`
120+
);
121+
process.exit(1);
122+
});
123+
}
124+
125+
nodemon({
126+
script: path.join('src/server', 'index.js'),
127+
ext: 'js graphql',
128+
exec: 'babel-node'
129+
}).on('restart', () => console.log('Restarting server due to file change\n'));
130+
131+
// Ensure stopping our parent process will properly kill nodemon's process
132+
// Ala https://www.exratione.com/2013/05/die-child-process-die/
133+
134+
// SIGTERM AND SIGINT will trigger the exit event.
135+
process.once('SIGTERM', function() {
136+
process.exit(0);
137+
});
138+
process.once('SIGINT', function() {
139+
process.exit(0);
140+
});
141+
// And the exit event shuts down the child.
142+
process.once('exit', function() {
143+
nodemon.emit('SIGINT');
144+
});

api/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "training-api",
3+
"version": "0.1.0",
4+
"description": "",
5+
"scripts": {
6+
"start": "babel-node index.js"
7+
},
8+
"author": "OK GROW! (Xavier Cazalot @xav_cz & Robert Dickert @rdickert)",
9+
"devDependencies": {
10+
"babel-cli": "^6.24.0",
11+
"babel-eslint": "^7.2.3",
12+
"babel-plugin-transform-async-generator-functions": "^6.24.1",
13+
"babel-plugin-transform-object-rest-spread": "^6.23.0",
14+
"babel-plugin-transform-runtime": "^6.23.0",
15+
"babel-preset-env": "^1.6.0",
16+
"denodeify": "^1.2.1",
17+
"eslint": "^4.6.1",
18+
"eslint-config-prettier": "^2.4.0",
19+
"mongodb-prebuilt": "5.0.7",
20+
"nodemon": "^1.11.0",
21+
"prettier": "^1.7.0"
22+
},
23+
"dependencies": {
24+
"apollo-engine": "^0.8.1",
25+
"apollo-link": "^0.7.0",
26+
"apollo-link-http": "^0.7.0",
27+
"apollo-server-express": "^1.1.0",
28+
"body-parser": "^1.17.1",
29+
"cors": "^2.8.4",
30+
"dataloader": "^1.3.0",
31+
"dotenv-safe": "^4.0.4",
32+
"express": "^4.15.2",
33+
"graphql": "^0.11.7",
34+
"graphql-subscriptions": "^0.5.1",
35+
"graphql-tools": "^2.2.1",
36+
"jwt-simple": "^0.5.1",
37+
"lodash.merge": "^4.6.0",
38+
"mongodb": "^2.2.31",
39+
"node-fetch": "^1.7.3",
40+
"node-geocoder": "^3.20.0",
41+
"nodeify": "^1.0.1",
42+
"passport": "^0.3.2",
43+
"passport-jwt": "^2.2.1",
44+
"subscriptions-transport-ws": "^0.9.0"
45+
}
46+
}

api/src/model/Location.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import NodeGeocoder from 'node-geocoder';
2+
3+
const { GOOGLE_API_KEY } = process.env;
4+
5+
const geocoder = NodeGeocoder({
6+
provider: 'google',
7+
apiKey: GOOGLE_API_KEY,
8+
});
9+
10+
// basic loader making network request & returning a Promise
11+
const loader = {
12+
load: name => geocoder.geocode(name),
13+
};
14+
15+
export default class Location {
16+
async get(name) {
17+
if (!name) {
18+
return;
19+
}
20+
21+
try {
22+
const [location] = await loader.load(name);
23+
24+
return {
25+
...location,
26+
id: `${location.latitude}:${location.longitude}`,
27+
};
28+
} catch (e) {
29+
throw new Error(e);
30+
}
31+
}
32+
}

api/src/model/Place.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import DataLoader from 'dataloader';
2+
import { ObjectId } from 'mongodb';
3+
import { fromMongo, findByIds } from './utils';
4+
5+
export default class Place {
6+
constructor(context) {
7+
this.context = context;
8+
this.collection = context.db.collection('places');
9+
this.loader = new DataLoader(ids => findByIds(this.collection, ids));
10+
}
11+
12+
findOneById(id) {
13+
return this.loader.load(id);
14+
}
15+
16+
async all({ lastCreatedAt = 0, limit = 100 }) {
17+
const results = await this.collection
18+
.find({ createdAt: { $gt: lastCreatedAt } })
19+
.sort({ createdAt: 1 })
20+
.limit(limit)
21+
.toArray();
22+
23+
return results.map(fromMongo);
24+
}
25+
26+
async insert(doc) {
27+
const docToInsert = {
28+
...doc,
29+
createdAt: Date.now(),
30+
updatedAt: Date.now(),
31+
};
32+
33+
const { insertedId } = await this.collection.insertOne(docToInsert);
34+
35+
return insertedId;
36+
}
37+
38+
async updateById(id, doc) {
39+
const _id = new ObjectId(id);
40+
const ret = await this.collection.update(
41+
{ _id },
42+
{
43+
$set: {
44+
...doc,
45+
updatedAt: Date.now(),
46+
},
47+
}
48+
);
49+
this.loader.clear(id);
50+
51+
return ret;
52+
}
53+
54+
async removeById(id) {
55+
const _id = new ObjectId(id);
56+
const ret = await this.collection.remove({
57+
_id,
58+
});
59+
this.loader.clear(id);
60+
61+
return ret;
62+
}
63+
}

0 commit comments

Comments
 (0)