1
Fork 0

Add semantic ui, refactoring

This commit is contained in:
viktorstrate 2019-07-07 23:53:07 +02:00
parent 1604827f35
commit 02e657328f
16 changed files with 1913 additions and 6620 deletions

6
.gitignore vendored
View File

@ -6,8 +6,10 @@ node_modules/
# testing
/coverage
# production
build
# building
.cache/
dist/
build/
# misc
.DS_Store

View File

@ -1 +1,12 @@
{ "presets": ["env"] }
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3
}
]
],
"plugins": ["@babel/plugin-transform-spread"]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

@ -14,26 +14,38 @@
"author": "William Lyon",
"license": "MIT",
"dependencies": {
"apollo-boost": "^0.3.1",
"@babel/plugin-transform-spread": "^7.2.2",
"apollo-boost": "^0.4.3",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link-http": "^1.5.14",
"apollo-server": "^2.6.2",
"babel-runtime": "^6.26.0",
"body-parser": "^1.19.0",
"dotenv": "^7.0.0",
"core-js": "^3.1.4",
"dotenv": "^8.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",
"regenerator-runtime": "^0.13.2",
"uuid": "^3.3.2"
},
"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"
"@babel/cli": "^7.5.0",
"@babel/core": "^7.5.0",
"@babel/preset-env": "^7.5.0",
"husky": "^3.0.0",
"lint-staged": "^9.1.0",
"nodemon": "^1.18.11",
"prettier": "^1.18.2"
},
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true
},
"husky": {
"hooks": {

View File

@ -1,4 +1,3 @@
import { neo4jgraphql } from "neo4j-graphql-js";
import fs from "fs";
import path from "path";

View File

@ -1,17 +1,15 @@
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'
import uuid from 'uuid'
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'
// set environment variables from ../.env
dotenv.config();
dotenv.config()
const app = express();
const app = express()
app.use(bodyParser.json())
/*
@ -22,72 +20,23 @@ app.use(bodyParser.json())
* https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
*/
import users from './resolvers/users'
const schema = makeAugmentedSchema({
typeDefs,
config: {
auth: {
isAuthenticated: true,
hasRole: true
hasRole: true,
},
mutation: false
mutation: false,
},
resolvers: {
Mutation: {
async authorizeUser(root, args, ctx, info) {
let {username, password} = args
let session = ctx.driver.session()
let result = await session.run("MATCH (usr:User {username: {username}, password: {password} }) RETURN usr.id", {username, password})
if (result.records.length == 0) {
return {
success: false,
status: "Username or password was invalid",
token: null
}
}
const record = result.records[0]
const userId = record.get('usr.id')
const token = jwt.sign({id: userId}, process.env.JWT_SECRET)
return {
success: true,
status: "Authorized",
token
}
},
async registerUser(root, args, ctx, info) {
let {username, password} = args
let session = ctx.driver.session()
let result = await session.run("MATCH (usr:User {username: {username} }) RETURN usr", {username})
if (result.records.length > 0) {
return {
success: false,
status: "Username is already taken",
token: null
}
}
await session.run("CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n", {username, password, id: uuid()})
session.close()
return {
success: true,
status: "User created",
token: "yay"
}
}
}
}
});
...users.mutation,
},
},
})
/*
* Create a Neo4j driver instance to connect to the database
@ -95,12 +44,12 @@ const schema = makeAugmentedSchema({
* with fallback to defaults
*/
const driver = neo4j.driver(
process.env.NEO4J_URI || "bolt://localhost:7687",
process.env.NEO4J_URI || 'bolt://localhost:7687',
neo4j.auth.basic(
process.env.NEO4J_USER || "neo4j",
process.env.NEO4J_PASSWORD || "letmein"
process.env.NEO4J_USER || 'neo4j',
process.env.NEO4J_PASSWORD || 'letmein'
)
);
)
/*
* Create a new ApolloServer instance, serving the GraphQL schema
@ -109,22 +58,22 @@ const driver = neo4j.driver(
* generated resolvers to connect to the database.
*/
const server = new ApolloServer({
context: ({ req }) => Object.assign(req, {driver}),
context: ({ req }) => Object.assign(req, { driver }),
schema: schema,
introspection: true,
playground: true
});
playground: true,
})
// Specify port and path for GraphQL endpoint
const port = process.env.GRAPHQL_LISTEN_PORT || 4001;
const path = "/graphql";
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});
* 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}`);
});
app.listen({ port, path }, () => {
console.log(`GraphQL server ready at http://localhost:${port}${path}`)
})

View File

@ -0,0 +1,69 @@
import jwt from 'jsonwebtoken'
import uuid from 'uuid'
const mutation = {
async authorizeUser(root, args, ctx, info) {
let { username, password } = args
let session = ctx.driver.session()
let result = await session.run(
'MATCH (usr:User {username: {username}, password: {password} }) RETURN usr.id',
{ username, password }
)
if (result.records.length == 0) {
return {
success: false,
status: 'Username or password was invalid',
token: null,
}
}
const record = result.records[0]
const userId = record.get('usr.id')
const token = jwt.sign({ id: userId }, process.env.JWT_SECRET)
return {
success: true,
status: 'Authorized',
token,
}
},
async registerUser(root, args, ctx, info) {
let { username, password } = args
let session = ctx.driver.session()
let result = await session.run(
'MATCH (usr:User {username: {username} }) RETURN usr',
{ username }
)
if (result.records.length > 0) {
return {
success: false,
status: 'Username is already taken',
token: null,
}
}
await session.run(
'CREATE (n:User { username: {username}, password: {password}, id: {id} }) return n',
{ username, password, id: uuid() }
)
session.close()
return {
success: true,
status: 'User created',
token: 'yay',
}
},
}
export default {
mutation,
}

View File

@ -7,6 +7,8 @@ type User @isAuthenticated {
id: ID!
username: String!
albums: [Album] @relation(name: "OWNS", direction: "OUT")
rootPath: String! @hasRole(roles: [Admin])
admin: Boolean
}
type Album @isAuthenticated {
@ -23,6 +25,11 @@ type Photo @isAuthenticated {
album: Album! @relation(name: "CONTAINS", direction: "IN")
}
type SiteInfo {
signupEnabled: Boolean!
firstSignup: Boolean!
}
type AuthorizeResult {
success: Boolean!
status: String
@ -34,44 +41,9 @@ type Mutation {
registerUser(username: String!, password: String!): AuthorizeResult!
}
# 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 {
siteInfo: SiteInfo
}
# type Query {
# usersBySubstring(substring: String): [User]

View File

@ -1,21 +0,0 @@
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

@ -1,394 +0,0 @@
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
}
}
}
`;

1
api/src/test.js Normal file
View File

@ -0,0 +1 @@
const neo4j = require('neo4j-graphql-js')

View File

@ -3,33 +3,36 @@
"version": "0.0.1",
"description": "UI app for GRANDstack",
"dependencies": {
"@material-ui/core": "^3.9.3",
"@material-ui/icons": "^3.0.2",
"@babel/preset-env": "^7.5.0",
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link": "^1.2.12",
"apollo-link-context": "^1.0.18",
"apollo-link-error": "^1.1.11",
"apollo-link-http": "^1.5.15",
"babel-plugin-styled-components": "^1.10.6",
"graphql": "^14.2.1",
"graphql-tag": "^2.10.1",
"parcel-bundler": "^1.12.3",
"prettier": "^1.18.2",
"react": "^16.8.6",
"react-apollo": "^2.5.5",
"react-dom": "^16.8.6",
"react-router-dom": "^5.0.1",
"react-scripts": "^3.0.0"
"semantic-ui-react": "^0.87.2",
"styled-components": "^4.3.2"
},
"scripts": {
"start": "react-scripts start",
"start": "parcel start src/index.html",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"now-build": "react-scripts build"
},
"devDependencies": {
"husky": "^2.0.0",
"lint-staged": "^8.1.5"
"@babel/core": "^7.5.0",
"husky": "^3.0.0",
"lint-staged": "^9.1.0"
},
"husky": {
"hooks": {
@ -42,6 +45,19 @@
"semi": false,
"singleQuote": true
},
"babel": {
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry"
}
]
],
"plugins": [
"babel-plugin-styled-components"
]
},
"lint-staged": {
"*.{js,json,css,md,graphql}": [
"prettier --write",

View File

@ -2,6 +2,7 @@ import React, { Component } from 'react'
import gql from 'graphql-tag'
import { Mutation } from 'react-apollo'
import { Redirect } from 'react-router-dom'
import { Button, Form, Message, Container, Header } from 'semantic-ui-react'
const authorizeMutation = gql`
mutation Authorize($username: String!, $password: String!) {
@ -45,51 +46,53 @@ class LoginPage extends Component {
return (
<div>
<h1>Welcome</h1>
<Mutation
mutation={authorizeMutation}
onCompleted={data => {
const { success, token } = data.authorizeUser
<Container>
<Header as="h1" textAlign="center">
Welcome
</Header>
<Mutation
mutation={authorizeMutation}
onCompleted={data => {
const { success, token } = data.authorizeUser
if (success) {
localStorage.setItem('token', token)
window.location = '/'
}
}}
>
{(authorize, { loading, error, data }) => {
let signInBtn = <input type="submit" value="Sign in" />
if (success) {
localStorage.setItem('token', token)
window.location = '/'
}
}}
>
{(authorize, { loading, error, data }) => {
let errorMessage = null
if (data) {
if (!data.authorizeUser.success)
errorMessage = data.authorizeUser.status
}
if (loading) signInBtn = <span>Signing in...</span>
let status = ''
if (data) {
if (!data.authorizeUser.success)
status = data.authorizeUser.status
}
return (
<form onSubmit={e => this.signIn(e, authorize)}>
<label htmlFor="username-field">Username:</label>
<input
id="username-field"
onChange={e => this.handleChange(e, 'username')}
/>
<br />
<label htmlFor="password-field">Password:</label>
<input
id="password-field"
type="password"
onChange={e => this.handleChange(e, 'password')}
/>
<br />
{signInBtn}
<br />
<span>{status}</span>
</form>
)
}}
</Mutation>
return (
<Form
style={{ width: 500, margin: 'auto' }}
error={!!errorMessage}
onSubmit={e => this.signIn(e, authorize)}
loading={loading || (data && data.authorizeUser.success)}
>
<Form.Field>
<label>Username</label>
<input onChange={e => this.handleChange(e, 'username')} />
</Form.Field>
<Form.Field>
<label>Password</label>
<input
type="password"
onChange={e => this.handleChange(e, 'password')}
/>
</Form.Field>
<Message error content={errorMessage} />
<Button type="submit">Sign in</Button>
</Form>
)
}}
</Mutation>
</Container>
</div>
)
}

11
ui/src/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<script src="./index.js"></script>
</body>
</html>

View File

@ -6,6 +6,8 @@ import client from './apolloClient'
import { ApolloProvider } from 'react-apollo'
import { BrowserRouter as Router } from 'react-router-dom'
import 'semantic-ui/dist/semantic.min.css'
const Main = () => (
<ApolloProvider client={client}>
<Router>

File diff suppressed because it is too large Load Diff