Work towards authentication

This commit is contained in:
viktorstrate
2019-07-05 00:36:32 +02:00
commit d3f9ed8116
37 changed files with 36385 additions and 0 deletions

1
api/.babelrc Normal file
View File

@@ -0,0 +1 @@
{ "presets": ["env"] }

7
api/.env Normal file
View File

@@ -0,0 +1,7 @@
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=letmein
GRAPHQL_LISTEN_PORT=4001
GRAPHQL_URI=http://localhost:4001/graphql
JWT_SECRET=topSecretEpicJWTKEYThing

12
api/Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM node:10
RUN mkdir -p /app
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 4000
CMD ["npm", "start"]

54
api/README.md Normal file
View File

@@ -0,0 +1,54 @@
# GRANDstack Starter - GraphQL API
## Quick Start
Install dependencies:
```
npm install
```
Start the GraphQL service:
```
npm start
```
This will start the GraphQL service (by default on localhost:4000) where you can issue GraphQL requests or access GraphQL Playground in the browser:
![GraphQL Playground](img/graphql-playground.png)
## Configure
Set your Neo4j connection string and credentials in `.env`. For example:
*.env*
```
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=letmein
```
Note that grand-stack-starter does not currently bundle a distribution of Neo4j. You can download [Neo4j Desktop](https://neo4j.com/download/) and run locally for development, spin up a [hosted Neo4j Sandbox instance](https://neo4j.com/download/), run Neo4j in one of the [many cloud options](https://neo4j.com/developer/guide-cloud-deployment/), or [spin up Neo4j in a Docker container](https://neo4j.com/developer/docker/). Just be sure to update the Neo4j connection string and credentials accordingly in `.env`.
## Deployment
You can deploy to any service that hosts Node.js apps, but [Zeit Now](https://zeit.co/now) is a great easy to use service for hosting your app that has an easy to use free plan for small projects.
To deploy your GraphQL service on Zeit Now, first install [Now Desktop](https://zeit.co/download) - you'll need to provide an email address. Then run
```
now
```
to deploy your GraphQL service on Zeit Now. Once deployed you'll be given a fresh URL that represents the current state of your application where you can access your GraphQL endpoint and GraphQL Playgound. For example: https://grand-stack-starter-api-pqdeodpvok.now.sh/
## Seeding The Database
Optionally you can seed the GraphQL service by executing mutations that will write sample data to the database:
```
npm run seedDb
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

7866
api/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

48
api/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "grand-stack-starter-api",
"version": "0.0.1",
"description": "API app for GRANDstack",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start-dev": "./node_modules/.bin/nodemon --exec babel-node src/index.js",
"build": "babel src -d build; cp .env build; cp src/schema.graphql build",
"now-build": "babel src -d build; cp .env build; cp src/schema.graphql build",
"start": "npm run build && node build/index.js",
"seedDb": "./node_modules/.bin/babel-node src/seed/seed-db.js"
},
"author": "William Lyon",
"license": "MIT",
"dependencies": {
"apollo-boost": "^0.3.1",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link-http": "^1.5.14",
"apollo-server": "^2.6.2",
"body-parser": "^1.19.0",
"dotenv": "^7.0.0",
"graphql-tag": "^2.10.1",
"jsonwebtoken": "^8.5.1",
"neo4j-driver": "^1.7.3",
"neo4j-graphql-js": "^2.6.3",
"node-fetch": "^2.3.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.7.0",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"nodemon": "^1.18.11"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,json,css,md,graphql": [
"prettier --write",
"git add"
]
}
}

14
api/src/graphql-schema.js Normal file
View File

@@ -0,0 +1,14 @@
import { neo4jgraphql } from "neo4j-graphql-js";
import fs from "fs";
import path from "path";
/*
* Check for GRAPHQL_SCHEMA environment variable to specify schema file
* fallback to schema.graphql if GRAPHQL_SCHEMA environment variable is not set
*/
export const typeDefs = fs
.readFileSync(
process.env.GRAPHQL_SCHEMA || path.join(__dirname, "schema.graphql")
)
.toString("utf-8");

80
api/src/index.js Normal file
View File

@@ -0,0 +1,80 @@
import { typeDefs } from "./graphql-schema";
import { ApolloServer } from "apollo-server-express";
import express from "express";
import bodyParser from "body-parser"
import { v1 as neo4j } from "neo4j-driver";
import { makeAugmentedSchema } from "neo4j-graphql-js";
import dotenv from "dotenv";
import jwt from 'jsonwebtoken'
// set environment variables from ../.env
dotenv.config();
const app = express();
app.use(bodyParser.json())
/*
* Create an executable GraphQL schema object from GraphQL type definitions
* including autogenerated queries and mutations.
* Optionally a config object can be included to specify which types to include
* in generated queries and/or mutations. Read more in the docs:
* https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
*/
const schema = makeAugmentedSchema({
typeDefs,
config: {
auth: {
isAuthenticated: true,
hasRole: true
}
},
resolvers: {
Mutation: {
authorizeUser(root, args, context, info) {
const token = jwt.sign({name: args.name}, process.env.JWT_SECRET)
return token
}
}
}
});
/*
* Create a Neo4j driver instance to connect to the database
* using credentials specified as environment variables
* with fallback to defaults
*/
const driver = neo4j.driver(
process.env.NEO4J_URI || "bolt://localhost:7687",
neo4j.auth.basic(
process.env.NEO4J_USER || "neo4j",
process.env.NEO4J_PASSWORD || "letmein"
)
);
/*
* Create a new ApolloServer instance, serving the GraphQL schema
* created using makeAugmentedSchema above and injecting the Neo4j driver
* instance into the context object so it is available in the
* generated resolvers to connect to the database.
*/
const server = new ApolloServer({
context: ({ req }) => Object.assign(req, {driver}),
schema: schema,
introspection: true,
playground: true
});
// Specify port and path for GraphQL endpoint
const port = process.env.GRAPHQL_LISTEN_PORT || 4001;
const path = "/graphql";
/*
* Optionally, apply Express middleware for authentication, etc
* This also also allows us to specify a path for the GraphQL endpoint
*/
server.applyMiddleware({app, path});
app.listen({port, path}, () => {
console.log(`GraphQL server ready at http://localhost:${port}${path}`);
});

59
api/src/schema.graphql Normal file
View File

@@ -0,0 +1,59 @@
enum Role {
Admin
User
}
type Todo @isAuthenticated {
id: ID!
title: String
}
type Mutation {
authorizeUser(name: String!): String
}
# type User {
# id: ID!
# name: String
# friends: [User] @relation(name: "FRIENDS", direction: "BOTH")
# reviews: [Review] @relation(name: "WROTE", direction: "OUT")
# avgStars: Float
# @cypher(
# statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN toFloat(avg(r.stars))"
# )
# numReviews: Int
# @cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN COUNT(r)")
# recommendations(first: Int = 3): [Business] @cypher(statement: "MATCH (this)-[:WROTE]->(r:Review)-[:REVIEWS]->(:Business)<-[:REVIEWS]-(:Review)<-[:WROTE]-(:User)-[:WROTE]->(:Review)-[:REVIEWS]->(rec:Business) WHERE NOT EXISTS( (this)-[:WROTE]->(:Review)-[:REVIEWS]->(rec) )WITH rec, COUNT(*) AS num ORDER BY num DESC LIMIT $first RETURN rec")
# }
# type Business {
# id: ID!
# name: String
# address: String
# city: String
# state: String
# avgStars: Float @cypher(statement: "MATCH (this)<-[:REVIEWS]-(r:Review) RETURN coalesce(avg(r.stars),0.0)")
# reviews: [Review] @relation(name: "REVIEWS", direction: "IN")
# categories: [Category] @relation(name: "IN_CATEGORY", direction: "OUT")
# }
# type Review {
# id: ID!
# stars: Int
# text: String
# date: Date
# business: Business @relation(name: "REVIEWS", direction: "OUT")
# user: User @relation(name: "WROTE", direction: "IN")
# }
# type Category {
# name: ID!
# businesses: [Business] @relation(name: "IN_CATEGORY", direction: "IN")
# }
# type Query {
# usersBySubstring(substring: String): [User]
# @cypher(
# statement: "MATCH (u:User) WHERE u.name CONTAINS $substring RETURN u"
# )
# }

21
api/src/seed/seed-db.js Normal file
View File

@@ -0,0 +1,21 @@
import ApolloClient from "apollo-client";
import gql from "graphql-tag";
import dotenv from "dotenv";
import seedmutations from "./seed-mutations";
import fetch from "node-fetch";
import { HttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
dotenv.config();
const client = new ApolloClient({
link: new HttpLink({ uri: process.env.GRAPHQL_URI, fetch }),
cache: new InMemoryCache()
});
client
.mutate({
mutation: gql(seedmutations)
})
.then(data => console.log(data))
.catch(error => console.error(error));

View File

@@ -0,0 +1,394 @@
export default /* GraphQL */ `
mutation {
u1: CreateUser(id: "u1", name: "Will") {
id
name
}
u2: CreateUser(id: "u2", name: "Bob") {
id
name
}
u3: CreateUser(id: "u3", name: "Jenny") {
id
name
}
u4: CreateUser(id: "u4", name: "Angie") {
id
name
}
b1: CreateBusiness(
id: "b1"
name: "KettleHouse Brewing Co."
address: "313 N 1st St W"
city: "Missoula"
state: "MT"
) {
id
name
}
b2: CreateBusiness(
id: "b2"
name: "Imagine Nation Brewing"
address: "1151 W Broadway St"
city: "Missoula"
state: "MT"
) {
id
name
}
b3: CreateBusiness(
id: "b3"
name: "Ninja Mike's"
address: "Food Truck - Farmers Market"
city: "Missoula"
state: "MT"
) {
id
name
}
b4: CreateBusiness(
id: "b4"
name: "Market on Front"
address: "201 E Front St"
city: "Missoula"
state: "MT"
) {
id
name
}
b5: CreateBusiness(
id: "b5"
name: "Missoula Public Library"
address: "301 E Main St"
city: "Missoula"
state: "MT"
) {
id
name
}
b6: CreateBusiness(
id: "b6"
name: "Zootown Brew"
address: "121 W Broadway St"
city: "Missoula"
state: "MT"
) {
id
name
}
b7: CreateBusiness(
id: "b7"
name: "Hanabi"
address: "723 California Dr"
city: "Burlingame"
state: "CA"
) {
id
name
}
b8: CreateBusiness(
id: "b8"
name: "Philz Coffee"
address: "113 B St"
city: "San Mateo"
state: "CA"
) {
id
name
}
b9: CreateBusiness(
id: "b9"
name: "Alpha Acid Brewing Company"
address: "121 Industrial Rd #11"
city: "Belmont"
state: "CA"
) {
id
name
}
b10: CreateBusiness(
id: "b10"
name: "San Mateo Public Library Central Library"
address: "55 W 3rd Ave"
city: "San Mateo"
state: "CA"
) {
id
name
}
c1: CreateCategory(name: "Coffee") {
name
}
c2: CreateCategory(name: "Library") {
name
}
c3: CreateCategory(name: "Beer") {
name
}
c4: CreateCategory(name: "Restaurant") {
name
}
c5: CreateCategory(name: "Ramen") {
name
}
c6: CreateCategory(name: "Cafe") {
name
}
c7: CreateCategory(name: "Deli") {
name
}
c8: CreateCategory(name: "Breakfast") {
name
}
c9: CreateCategory(name: "Brewery") {
name
}
a1: AddBusinessCategories(from: { id: "b1" }, to: { name: "Beer" }) {
from {
id
}
}
a1a: AddBusinessCategories(from: { id: "b1" }, to: { name: "Brewery" }) {
from {
id
}
}
a2: AddBusinessCategories(from: { id: "b2" }, to: { name: "Beer" }) {
from {
id
}
}
a2a: AddBusinessCategories(from: { id: "b2" }, to: { name: "Brewery" }) {
from {
id
}
}
a3: AddBusinessCategories(from: { id: "b3" }, to: { name: "Restaurant" }) {
from {
id
}
}
a4: AddBusinessCategories(from: { id: "b3" }, to: { name: "Breakfast" }) {
from {
id
}
}
a5: AddBusinessCategories(from: { id: "b4" }, to: { name: "Coffee" }) {
from {
id
}
}
a5a: AddBusinessCategories(from: { id: "b4" }, to: { name: "Restaurant" }) {
from {
id
}
}
a5b: AddBusinessCategories(from: { id: "b4" }, to: { name: "Cafe" }) {
from {
id
}
}
a5c: AddBusinessCategories(from: { id: "b4" }, to: { name: "Deli" }) {
from {
id
}
}
a5d: AddBusinessCategories(from: { id: "b4" }, to: { name: "Breakfast" }) {
from {
id
}
}
a6: AddBusinessCategories(from: { id: "b5" }, to: { name: "Library" }) {
from {
id
}
}
a7: AddBusinessCategories(from: { id: "b6" }, to: { name: "Coffee" }) {
from {
id
}
}
a8: AddBusinessCategories(from: { id: "b7" }, to: { name: "Restaurant" }) {
from {
id
}
}
a8a: AddBusinessCategories(from: { id: "b7" }, to: { name: "Ramen" }) {
from {
id
}
}
a9: AddBusinessCategories(from: { id: "b8" }, to: { name: "Coffee" }) {
from {
id
}
}
a9a: AddBusinessCategories(from: { id: "b8" }, to: { name: "Breakfast" }) {
from {
id
}
}
a10: AddBusinessCategories(from: { id: "b9" }, to: { name: "Brewery" }) {
from {
id
}
}
a11: AddBusinessCategories(from: { id: "b10" }, to: { name: "Library" }) {
from {
id
}
}
r1: CreateReview(id: "r1", stars: 4, text: "Great IPA selection!", date: { formatted: "2016-01-03"}) {
id
}
ar1: AddUserReviews(from: { id: "u1" }, to: { id: "r1" }) {
from {
id
}
}
ab1: AddReviewBusiness(from: { id: "r1" }, to: { id: "b1" }) {
from {
id
}
}
r2: CreateReview(id: "r2", stars: 5, text: "", date: { formatted: "2016-07-14"}) {
id
}
ar2: AddUserReviews(from: { id: "u3" }, to: { id: "r2" }) {
from {
id
}
}
ab2: AddReviewBusiness(from: { id: "r2" }, to: { id: "b1" }) {
from {
id
}
}
r3: CreateReview(id: "r3", stars: 3, text: "", date: { formatted: "2018-09-10"}) {
id
}
ar3: AddUserReviews(from: { id: "u4" }, to: { id: "r3" }) {
from {
id
}
}
ab3: AddReviewBusiness(from: { id: "r3" }, to: { id: "b2" }) {
from {
id
}
}
r4: CreateReview(id: "r4", stars: 5, text: "", date: { formatted: "2017-11-13"}) {
id
}
ar4: AddUserReviews(from: { id: "u3" }, to: { id: "r4" }) {
from {
id
}
}
ab4: AddReviewBusiness(from: { id: "r4" }, to: { id: "b3" }) {
from {
id
}
}
r5: CreateReview(
id: "r5"
stars: 4
text: "Best breakfast sandwich at the Farmer's Market. Always get the works."
date: { formatted: "2018-01-03"}
) {
id
}
ar5: AddUserReviews(from: { id: "u1" }, to: { id: "r5" }) {
from {
id
}
}
ab5: AddReviewBusiness(from: { id: "r5" }, to: { id: "b3" }) {
from {
id
}
}
r6: CreateReview(id: "r6", stars: 4, text: "", date: { formatted: "2018-03-24"}) {
id
}
ar6: AddUserReviews(from: { id: "u2" }, to: { id: "r6" }) {
from {
id
}
}
ab6: AddReviewBusiness(from: { id: "r6" }, to: { id: "b4" }) {
from {
id
}
}
r7: CreateReview(
id: "r7"
stars: 3
text: "Not a great selection of books, but fortunately the inter-library loan system is good. Wifi is quite slow. Not many comfortable places to site and read. Looking forward to the new building across the street in 2020!"
date: { formatted: "2015-08-29"}
) {
id
}
ar7: AddUserReviews(from: { id: "u1" }, to: { id: "r7" }) {
from {
id
}
}
ab7: AddReviewBusiness(from: { id: "r7" }, to: { id: "b5" }) {
from {
id
}
}
r8: CreateReview(id: "r8", stars: 5, text: "", date: { formatted: "2018-08-11"}) {
id
}
ar8: AddUserReviews(from: { id: "u4" }, to: { id: "r8" }) {
from {
id
}
}
ab8: AddReviewBusiness(from: { id: "r8" }, to: { id: "b6" }) {
from {
id
}
}
r9: CreateReview(id: "r9", stars: 5, text: "", date: { formatted: "2016-11-21"}) {
id
}
ar9: AddUserReviews(from: { id: "u3" }, to: { id: "r9" }) {
from {
id
}
}
ab9: AddReviewBusiness(from: { id: "r9" }, to: { id: "b7" }) {
from {
id
}
}
r10: CreateReview(id: "r10", stars: 4, text: "", date: { formatted: "2015-12-15"}) {
id
}
ar10: AddUserReviews(from: { id: "u2" }, to: { id: "r10" }) {
from {
id
}
}
ab10: AddReviewBusiness(from: { id: "r10" }, to: { id: "b2" }) {
from {
id
}
}
}
`;