Work towards authentication
This commit is contained in:
commit
d3f9ed8116
|
@ -0,0 +1,21 @@
|
|||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
yarn.lock
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,109 @@
|
|||
[![Deploy to now](https://deploy.now.sh/static/button.svg)](https://deploy.now.sh/?repo=https://github.com/grand-stack/grand-stack-starter&env=NEO4J_USER&env=NEO4J_URI&env=NEO4J_PASSWORD)
|
||||
|
||||
# GRANDstack Starter
|
||||
|
||||
This project is a starter for building a [GRANDstack](https://grandstack.io) (GraphQL, React, Apollo, Neo4j Database) application. There are two components to the starter, the UI application (a React app) and the API app (GraphQL server).
|
||||
|
||||
[![Hands On With The GRANDstack Starter](http://img.youtube.com/vi/rPC71lUhK_I/0.jpg)](http://www.youtube.com/watch?v=rPC71lUhK_I "Hands On With The GRANDstack Starter")
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Neo4j
|
||||
|
||||
You need a Neo4j instance, e.g. a [Neo4j Sandbox](http://neo4j.com/sandbox), a local instance via [Neo4j Desktop](https://neo4j.com/download), [Docker](http://hub.docker.com/_/neo4j) or a [Neo4j instance on AWS, Azure or GCP](http://neo4j.com/developer/guide-cloud-deployment) or [Neo4j Cloud](http://neo4j.com/cloud)
|
||||
|
||||
For schemas using the `@cypher` directive (as in this repo) via [`neo4j-graphql-js`](https://github.com/neo4j-graphql/neo4j-graphql-js), you need to have the [APOC library](https://github.com/neo4j-contrib/neo4j-apoc-procedures) installed, which should be automatic in Sandbox, Cloud and is a single click install in Neo4j Desktop. If when using the Sandbox / cloud you encounter an issue where an error similar to `Can not be converted to long: org.neo4j.kernel.impl.core.NodeProxy, Location: [object Object], Path: users` appears in the console when running the React app, try installing and using Neo4j locally instead.
|
||||
|
||||
#### Sandbox setup
|
||||
A good tutorial can be found here: https://www.youtube.com/watch?v=rPC71lUhK_I
|
||||
|
||||
#### Local setup
|
||||
1. [Download Neo4j Desktop](https://neo4j.com/download/)
|
||||
2. Install and open Neo4j Desktop.
|
||||
3. Create a new DB by clicking "New Graph", and clicking "create local graph".
|
||||
4. Set password to "letmein" (as suggested by `api/.env`), and click "Create".
|
||||
5. Make sure that the default credentials in `api/.env` are used. Leave them as follows: `NEO4J_URI=bolt://localhost:7687 NEO4J_USER=neo4j NEO4J_PASSWORD=letmein`
|
||||
6. Click "Manage".
|
||||
7. Click "Plugins".
|
||||
8. Find "APOC" and click "Install".
|
||||
9. Click the "play" button at the top of left the screen, which should start the server. _(screenshot 2)_
|
||||
10. Wait until it says "RUNNING".
|
||||
11. Proceed forward with the rest of the tutorial.
|
||||
|
||||
### [`/api`](./api)
|
||||
|
||||
*Install dependencies*
|
||||
|
||||
```
|
||||
(cd ./ui && npm install)
|
||||
(cd ./api && npm install)
|
||||
```
|
||||
|
||||
*Start API server*
|
||||
```
|
||||
cd ./api && npm start
|
||||
```
|
||||
|
||||
![](api/img/graphql-playground.png)
|
||||
|
||||
### [`/ui`](./ui)
|
||||
|
||||
This will start the GraphQL API in the foreground, so in another terminal session start the UI development server:
|
||||
|
||||
*Start UI server*
|
||||
```
|
||||
cd ./ui && npm start
|
||||
```
|
||||
|
||||
![](ui/img/default-app.png)
|
||||
|
||||
See [the project releases](https://github.com/grand-stack/grand-stack-starter/releases) for the changelog.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Zeit Now v2
|
||||
|
||||
Zeit Now v2 can be used with monorepos such as grand-stack-starter. [`now.json`](https://github.com/grand-stack/grand-stack-starter/blob/master/now.json) defines the configuration for deploying with Zeit Now v2.
|
||||
|
||||
1. Set the now secrets for your Neo4j instance:
|
||||
|
||||
```
|
||||
now secret add NEO4J_URI bolt+routing://<YOUR_NEO4J_INSTANCE_HERE>
|
||||
now secret add NEO4J_USER <YOUR_DATABASE_USERNAME_HERE>
|
||||
now secret add NEO4J_PASSWORD <YOUR_DATABASE_USER_PASSWORD_HERE>
|
||||
```
|
||||
|
||||
2. Run `now`
|
||||
|
||||
### Zeit Now v1
|
||||
|
||||
1. Run `now` in `/api` and choose `package.json` when prompted.
|
||||
1. Set `REACT_APP_GRAPHQL_API` based on the API deployment URL from step 1 in `ui/.env`
|
||||
1. Run `now` in `/env` and choose `package.json` when prompted.
|
||||
|
||||
## Docker Compose
|
||||
|
||||
To use docker-compose to quickly start please make the following changes
|
||||
|
||||
api/.env:
|
||||
```
|
||||
NEO4J_URI=bolt://neo4j:7687
|
||||
NEO4J_USER=neo4j
|
||||
NEO4J_PASSWORD=letmein
|
||||
GRAPHQL_LISTEN_PORT=4000
|
||||
GRAPHQL_URI=http://api:4000
|
||||
```
|
||||
|
||||
Now you can quickly start via:
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
If you want to load the example DB after the services have been started:
|
||||
```
|
||||
docker-compose run api npm run seedDb
|
||||
```
|
||||
|
||||
|
||||
This project is licensed under the Apache License v2.
|
||||
Copyright (c) 2018 Neo4j, Inc.
|
|
@ -0,0 +1 @@
|
|||
{ "presets": ["env"] }
|
|
@ -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
|
|
@ -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"]
|
|
@ -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 |
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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");
|
|
@ -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}`);
|
||||
});
|
|
@ -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"
|
||||
# )
|
||||
# }
|
|
@ -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));
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,32 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
neo4j:
|
||||
build: ./neo4j
|
||||
ports:
|
||||
- 7474:7474
|
||||
- 7687:7687
|
||||
environment:
|
||||
- NEO4J_dbms_security_procedures_unrestricted=apoc.*
|
||||
- NEO4J_apoc_import_file_enabled=true
|
||||
- NEO4J_apoc_export_file_enabled=true
|
||||
- NEO4J_dbms_shell_enabled=true
|
||||
|
||||
api:
|
||||
build: ./api
|
||||
ports:
|
||||
- 4000:4000
|
||||
links:
|
||||
- neo4j
|
||||
depends_on:
|
||||
- neo4j
|
||||
|
||||
ui:
|
||||
build: ./ui
|
||||
ports:
|
||||
- 3000:3000
|
||||
links:
|
||||
- api
|
||||
depends_on:
|
||||
- api
|
|
@ -0,0 +1,15 @@
|
|||
FROM neo4j:3.4.5
|
||||
|
||||
ENV NEO4J_AUTH=neo4j/letmein
|
||||
|
||||
ENV APOC_VERSION 3.4.0.2
|
||||
ENV APOC_URI https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/${APOC_VERSION}/apoc-${APOC_VERSION}-all.jar
|
||||
RUN wget -P /var/lib/neo4j/plugins ${APOC_URI}
|
||||
|
||||
ENV GRAPHQL_VERSION 3.4.0.1
|
||||
ENV GRAPHQL_URI https://github.com/neo4j-graphql/neo4j-graphql/releases/download/${GRAPHQL_VERSION}/neo4j-graphql-${GRAPHQL_VERSION}.jar
|
||||
RUN wget -P /var/lib/neo4j/plugins ${GRAPHQL_URI}
|
||||
|
||||
EXPOSE 7474 7473 7687
|
||||
|
||||
CMD ["neo4j"]
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"version": 2,
|
||||
"name": "grand-stack-starter",
|
||||
"builds": [
|
||||
{ "src": "api/src/index.js", "use": "@now/node-server" },
|
||||
{
|
||||
"src": "ui/package.json",
|
||||
"use": "@now/static-build",
|
||||
"config": {"distDir": "build"}
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{ "src": "/graphql(.*)", "dest": "api/src/index.js" },
|
||||
{
|
||||
"src": "/static/(.*)",
|
||||
"headers": { "cache-control": "s-maxage=31536000,immutable" },
|
||||
"dest": "ui/static/$1"
|
||||
},
|
||||
{ "src": "/favicon.ico", "dest": "ui/favicon.ico" },
|
||||
{ "src": "/img/(.*)", "dest": "ui/img/$1"},
|
||||
{ "src": "/asset-manifest.json", "dest": "ui/asset-manifest.json" },
|
||||
{ "src": "/manifest.json", "dest": "ui/manifest.json" },
|
||||
{ "src": "/precache-manifest.(.*)", "dest": "ui/precache-manifest.$1" },
|
||||
{
|
||||
"src": "/service-worker.js",
|
||||
"headers": { "cache-control": "s-maxage=0" },
|
||||
"dest": "ui/service-worker.js"
|
||||
},
|
||||
{
|
||||
"src": "^(.*)$",
|
||||
"headers": { "cache-control": "s-maxage=0" },
|
||||
"dest": "/ui/index.html"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"REACT_APP_GRAPHQL_URI": "/graphql",
|
||||
"NEO4J_URI": "@neo4j_uri",
|
||||
"NEO4J_USER": "@neo4j_user",
|
||||
"NEO4J_PASSWORD": "@neo4j_password"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Chrome",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceRoot}/src"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
FROM node:10
|
||||
|
||||
RUN mkdir -p /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json .
|
||||
RUN npm install
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "start"]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
client: {
|
||||
service: {
|
||||
name: 'localhost graphql',
|
||||
url: 'http://localhost:4001/graphql',
|
||||
skipSSLValidation: true
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "grand-stack-starter-ui",
|
||||
"version": "0.0.1",
|
||||
"description": "UI app for GRANDstack",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^3.9.3",
|
||||
"@material-ui/icons": "^3.0.2",
|
||||
"apollo-boost": "^0.3.1",
|
||||
"graphql": "^14.2.1",
|
||||
"graphql-tag": "^2.10.1",
|
||||
"prettier": "^1.17.0",
|
||||
"react": "^16.8.6",
|
||||
"react-apollo": "^2.5.5",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-scripts": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"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"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,json,css,md,graphql}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/icon?family=Material+Icons"
|
||||
/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import React, { Component } from "react";
|
||||
import gql from 'graphql-tag'
|
||||
import { Query } from 'react-apollo'
|
||||
|
||||
class App extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Todo App</h1>
|
||||
<Query query={gql`
|
||||
query Todos {
|
||||
Todo {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
`}>
|
||||
{({data, loading, error}) => {
|
||||
if (loading) return <div>Loading todos...</div>
|
||||
if (error) return <div>Error</div>
|
||||
|
||||
let todos = data.Todo.map(todo => <li key={todo.id}>{todo.title}</li>)
|
||||
|
||||
return <ul>{todos}</ul>
|
||||
}}
|
||||
</Query>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import App from "./App";
|
||||
|
||||
it("renders without crashing", () => {
|
||||
const div = document.createElement("div");
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import "./index.css";
|
||||
import App from "./App";
|
||||
import registerServiceWorker from "./registerServiceWorker";
|
||||
import ApolloClient from "apollo-boost";
|
||||
import { ApolloProvider } from "react-apollo";
|
||||
|
||||
const client = new ApolloClient({
|
||||
uri: process.env.REACT_APP_GRAPHQL_URI
|
||||
});
|
||||
|
||||
const Main = () => (
|
||||
<ApolloProvider client={client}>
|
||||
<App />
|
||||
</ApolloProvider>
|
||||
);
|
||||
|
||||
ReactDOM.render(<Main />, document.getElementById("root"));
|
||||
registerServiceWorker();
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,117 @@
|
|||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === "localhost" ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === "[::1]" ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export default function register() {
|
||||
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
"This web app is being served cache-first by a service " +
|
||||
"worker. To learn more, visit https://goo.gl/SC7cgQ"
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === "installed") {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log("New content is available; please refresh.");
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log("Content is cached for offline use.");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error during service worker registration:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get("content-type").indexOf("javascript") === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode."
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue