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

Adding slack details #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# OK GROW! Training

> Slides: [http://training-slides.okgrow.com/advanced-graphql-GraphQL-Summit-2017-10-24/](http://training-slides.okgrow.com/advanced-graphql-GraphQL-Summit-2017-10-24/)

Look at the README of the `/api` & `/ui` folders to get started!

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.

Join us on slack!

1. Sign up for Apollo GraphQL Slack Here: https://www.apollodata.com/#slack
2. Join channel #graphql-summit
3. Say Hi
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.4.7",
"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",
"dotenv-safe": "^4.0.4",
"dataloader": "^1.3.0",
"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);
}
}
}
64 changes: 64 additions & 0 deletions api/src/model/Place.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import DataLoader from 'dataloader';
import { ObjectId } from 'mongodb';
import { fromMongo, findByIds } from './utils';

export default class Place {
constructor(context) {
this.context = context;
this.collection = context.db.collection('places');
this.pubsub = context.pubsub;
this.loader = new DataLoader(ids => findByIds(this.collection, ids));
}

findOneById(id) {
return this.loader.load(id);
}

async all({ lastCreatedAt = 0, limit = 100 }) {
const results = await this.collection
.find({ createdAt: { $gt: lastCreatedAt } })
.sort({ createdAt: 1 })
.limit(limit)
.toArray();

return results.map(fromMongo);
}

async insert(doc) {
const docToInsert = {
...doc,
createdAt: Date.now(),
updatedAt: Date.now(),
};

const { insertedId } = await this.collection.insertOne(docToInsert);

return insertedId;
}

async updateById(id, doc) {
const _id = new ObjectId(id);
const ret = await this.collection.update(
{ _id },
{
$set: {
...doc,
updatedAt: Date.now(),
},
}
);
this.loader.clear(id);
this.pubsub.publish('placeUpdated', { placeUpdated: { id, ...doc } });
return ret;
}

async removeById(id) {
const _id = new ObjectId(id);
const ret = await this.collection.remove({
_id,
});
this.loader.clear(id);

return ret;
}
}
Loading