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

Commit

Permalink
⊂(◕‿◕)つ ADVANCED GRAPHQL
Browse files Browse the repository at this point in the history
  • Loading branch information
xavxyz authored and pauldowman committed Mar 16, 2018
1 parent 6a40b58 commit e1e5727
Show file tree
Hide file tree
Showing 74 changed files with 12,892 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
insert_final_newline = true
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# OK GROW! Training

> If your computer is already set up, look at the `README` of the `/api` & `/ui` folders to get started!
## Computer Setup

Have a look at the software requirements and please install the missing ones.

### Versioning

We’ll use [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) to complete exercises.

### Code editor

We recommend [Atom](https://atom.io/) with the following packages installed:

* linter
* linter-eslint
* language-babel
* autocomplete-modules
* prettier

If you prefer to use another editor, please check for corresponding packages.

### Node.js

You’ll need Node.js installed. We recommend the use of [NVM](https://nvm.sh). You can also install it from the [official website](https://nodejs.org). We’ll use Node 8 (LTS).

Though Node comes with a package manager called npm, we’ll use [Yarn](https://yarnpkg.com), a modern deterministic packager (we’ll cover its benefits).

## Prerequisite knowledge

We will explore all of this thoroughly during the course, but if you are not comfortable with the following concepts, these links are worth a look.

### Must have - GraphQL culture

Some background on GraphQL: “[So what’s this GraphQL thing I keep hearing about?](https://medium.freecodecamp.com/so-whats-this-graphql-thing-i-keep-hearing-about-baf4d36c20cf)

We won’t cover GraphQL fundamentals, like the Schema Definition Language or how to send an operation from the client to the server.

We will be using Apollo for client and server GraphQL operations. If you are unfamiliar with these tools (you may be used to GraphQL in other languages or different client), you’ll find useful to have a look at the documentation of the [Apollo server tools](https://www.apollographql.com/docs/graphql-tools/) & the [React integration](https://www.apollographql.com/docs/react/).

### Must have - JavaScript basics

We won’t cover basic JS syntax. You should know at least how to write a function. If you don’t, please take this free course on [Codecademy](https://www.codecademy.com).

### Nice to have - ES2015+

Recent tools are making good use of the newest features in ES2015 and even ES2016 and ES2017. The newest JavaScript is a joy to use. If you haven't tried these, I hope you'll enjoy getting to know the new JS: http://es6-features.org/

Used in this class

* modules with import/export
* const and let
* arrow functions (also related: Function.bind(), which we'll see in React)
* destructuring, rest and spread operators
* template literals (everywhere, but a special use in GraphQL)
* async/await - APIs and Apollo
* Array.concat()

### Nice to have - React culture

We will be using [React](https://facebook.github.io/react/) to render client components.

One nice thing about React is that it is close to base JS, so you should be able to follow even if you are new to it. Nevertheless, some understanding of it will be highly useful. We will implement the GraphQL integration with higher-order components. We recommend that you read this post to understand the concept of container components, and then this post to understand generating container components using higher-order components.

If you want to go a little deeper and understand our perspective on higher-order-components, read [our blog post on the subject](https://www.okgrow.com/posts/compose-react-sphoc) and look at the [recompose](https://github.com/acdlite/recompose) library.

## Questions before the course starts

Depending on your current level, you can expect to spend up to several hours on this material. It's worth it, and you will get much more from the class. If anything here is confusing or you want to clarify a fine point, please get in touch. We are happy to answer your questions, before, during, and after the class.

Ping us on Twitter:

* Robert Dickert - [@rdickert](https://twitter.com/rdickert)
* Paul Dowman - [@pauldowman](https://twitter.com/pauldowman)

Or email us at [training@okgrow.com](mailto:training@okgrow.com)

## Code Quality

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.
8 changes: 8 additions & 0 deletions api/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
presets: ['env'],
plugins: [
'transform-runtime',
'transform-async-generator-functions',
'transform-object-rest-spread',
],
}
4 changes: 4 additions & 0 deletions api/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PORT=8080
GOOGLE_API_KEY=
DARKSKY_API_KEY=
ENGINE_API_KEY=
14 changes: 14 additions & 0 deletions api/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": ["prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 80
}
]
}
}
8 changes: 8 additions & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/db
/node_modules

npm-debug.log*
yarn-debug.log*
yarn-error.log*

.env
27 changes: 27 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# OK GROW! Training - API Server

```sh
# make a .env file from the example
cp .env.example .env
```

Update the .env file with your api keys:

* 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)
* [DARKSKY_API_KEY](https://darksky.net/dev)
* [ENGINE_API_KEY](https://engine.apollographql.com/)

```sh
# install the dependencies
yarn

# run the API server
yarn start
```

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/)):

```sh
# Usually not necessary
mongod
```
144 changes: 144 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import dotenv from 'dotenv-safe';
import nodemon from 'nodemon';
import fs from 'fs';
import path from 'path';
import mongoPrebuilt from 'mongodb-prebuilt';
import denodeify from 'denodeify';

const dbpath = `${__dirname}/db`;

dotenv.config();

const {
PORT = 8080,
MONGO_PORT = parseInt(PORT, 10) + 2,
MONGO_URL
} = process.env;

// Taken from https://github.com/meteor/meteor/blob/debug-circle-timeout-promise-await/tools/utils/mongo-exit-codes.js
const MONGO_CODES = {
0: {
code: 0,
symbol: 'EXIT_CLEAN',
longText: 'MongoDB exited cleanly'
},
1: {
code: 1,
// No symbol in the source. This is in src/mongo/base/initializer.cpp.
symbol: 'global-initialization',
longText: 'MongoDB failed global initialization'
},
2: {
code: 2,
symbol: 'EXIT_BADOPTIONS',
longText:
'MongoDB was started with erroneous or incompatible command line options'
},
3: {
code: 3,
symbol: 'EXIT_REPLICATION_ERROR',
longText:
'There was an inconsistency between hostnames specified\n' +
'on the command line compared with hostnames stored in local.sources'
},
4: {
code: 4,
symbol: 'EXIT_NEED_UPGRADE',
longText: 'MongoDB needs to upgrade to use this database'
},
5: {
code: 5,
symbol: 'EXIT_SHARDING_ERROR',
longText: 'A moveChunk operation failed'
},
12: {
code: 12,
symbol: 'EXIT_KILL',
longText: 'The MongoDB process was killed, on Windows'
},
14: {
code: 14,
symbol: 'EXIT_ABRUPT',
longText: 'Unspecified unrecoverable error. Exit was not clean'
},
20: {
code: 20,
symbol: 'EXIT_NTSERVICE_ERROR',
longText: 'Error managing NT Service on Windows'
},
45: {
code: 45,
symbol: 'EXIT_FS',
longText: 'MongoDB cannot open or obtain a lock on a file'
},
47: {
code: 47,
symbol: 'EXIT_CLOCK_SKEW',
longText: 'MongoDB exited due to excess clock skew'
},
48: {
code: 48,
symbol: 'EXIT_NET_ERROR',
longText:
'MongoDB exited because its port was closed, or was already\n' +
'taken by a previous instance of MongoDB'
},
100: {
code: 100,
symbol: 'EXIT_UNCAUGHT',
longText:
'MongoDB had an unspecified uncaught exception.\n' +
'This can be caused by MongoDB being unable to write to a local database.\n' +
`Check that you have permissions to write to ${dbpath}. MongoDB does\n` +
'not support filesystems like NFS that do not allow file locking.'
}
};

if (!MONGO_URL) {
console.log(
`Creating development MongoDB on mongodb://localhost:${MONGO_PORT}`
);

if (!fs.existsSync(dbpath)) {
fs.mkdirSync(dbpath);
}

// Weirdly, this promise never resolves if Mongo starts.
// However, we'll just go ahead and start the node server anyway,
// and if we see an error, we'll quit
denodeify(mongoPrebuilt.start_server.bind(mongoPrebuilt))({
auto_shutdown: true,
args: {
port: MONGO_PORT,
dbpath
}
}).catch(errorCode => {
const error = MONGO_CODES[errorCode];
console.error(`Failed to start MongoDB server on port ${MONGO_PORT}`);
console.error(
`Error Code ${errorCode}: ${error ? error.longText : 'Unknown'}`
);
process.exit(1);
});
}

nodemon({
script: path.join('src/server', 'index.js'),
ext: 'js graphql',
exec: 'babel-node'
}).on('restart', () => console.log('Restarting server due to file change\n'));

// Ensure stopping our parent process will properly kill nodemon's process
// Ala https://www.exratione.com/2013/05/die-child-process-die/

// SIGTERM AND SIGINT will trigger the exit event.
process.once('SIGTERM', function() {
process.exit(0);
});
process.once('SIGINT', function() {
process.exit(0);
});
// And the exit event shuts down the child.
process.once('exit', function() {
nodemon.emit('SIGINT');
});
46 changes: 46 additions & 0 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "training-api",
"version": "0.1.0",
"description": "",
"scripts": {
"start": "babel-node index.js"
},
"author": "OK GROW! (Xavier Cazalot @xav_cz & Robert Dickert @rdickert)",
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-eslint": "^7.2.3",
"babel-plugin-transform-async-generator-functions": "^6.24.1",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"denodeify": "^1.2.1",
"eslint": "^4.6.1",
"eslint-config-prettier": "^2.4.0",
"mongodb-prebuilt": "5.0.7",
"nodemon": "^1.11.0",
"prettier": "^1.7.0"
},
"dependencies": {
"apollo-engine": "^0.8.1",
"apollo-link": "^0.7.0",
"apollo-link-http": "^0.7.0",
"apollo-server-express": "^1.1.0",
"body-parser": "^1.17.1",
"cors": "^2.8.4",
"dataloader": "^1.3.0",
"dotenv-safe": "^4.0.4",
"express": "^4.15.2",
"graphql": "^0.11.7",
"graphql-subscriptions": "^0.5.1",
"graphql-tools": "^2.2.1",
"jwt-simple": "^0.5.1",
"lodash.merge": "^4.6.0",
"mongodb": "^2.2.31",
"node-fetch": "^1.7.3",
"node-geocoder": "^3.20.0",
"nodeify": "^1.0.1",
"passport": "^0.3.2",
"passport-jwt": "^2.2.1",
"subscriptions-transport-ws": "^0.9.0"
}
}
32 changes: 32 additions & 0 deletions api/src/model/Location.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import NodeGeocoder from 'node-geocoder';

const { GOOGLE_API_KEY } = process.env;

const geocoder = NodeGeocoder({
provider: 'google',
apiKey: GOOGLE_API_KEY,
});

// basic loader making network request & returning a Promise
const loader = {
load: name => geocoder.geocode(name),
};

export default class Location {
async get(name) {
if (!name) {
return;
}

try {
const [location] = await loader.load(name);

return {
...location,
id: `${location.latitude}:${location.longitude}`,
};
} catch (e) {
throw new Error(e);
}
}
}
Loading

0 comments on commit e1e5727

Please sign in to comment.