47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 vanbenj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ui/src/template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | %sapper.base%
9 |
10 |
11 |
12 |
13 |
14 |
17 | %sapper.styles%
18 |
19 |
21 | %sapper.head%
22 |
23 |
24 |
26 |
71 |
--------------------------------------------------------------------------------
/api/src/schema.graphql:
--------------------------------------------------------------------------------
1 | type User {
2 | id: ID!
3 | name: String
4 | friends: [User] @relation(name: "FRIENDS", direction: "BOTH")
5 | reviews: [Review] @relation(name: "WROTE", direction: "OUT")
6 | avgStars: Float
7 | @cypher(
8 | statement: "MATCH (this)-[:WROTE] RETURN toFloat(avg(r.stars))"
9 | )
10 | numReviews: Int
11 | @cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN COUNT(r)")
12 | 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")
13 | }
14 |
15 | type Business {
16 | id: ID!
17 | name: String
18 | address: String
19 | city: String
20 | state: String
21 | avgStars: Float @cypher(statement: "MATCH (this)<-[:REVIEWS]-(r:Review) RETURN coalesce(avg(r.stars),0.0)")
22 | reviews: [Review] @relation(name: "REVIEWS", direction: "IN")
23 | categories: [Category] @relation(name: "IN_CATEGORY", direction: "OUT")
24 | }
25 |
26 | type Review {
27 | id: ID!
28 | stars: Int
29 | text: String
30 | date: Date
31 | business: Business @relation(name: "REVIEWS", direction: "OUT")
32 | user: User @relation(name: "WROTE", direction: "IN")
33 | }
34 |
35 | type Category {
36 | name: ID!
37 | businesses: [Business] @relation(name: "IN_CATEGORY", direction: "IN")
38 | }
39 |
40 | type Query {
41 | usersBySubstring(substring: String): [User] @cypher(statement: "MATCH (u:User) WHERE u.name CONTAINS $substring RETURN u")
42 | hello: String
43 | }
44 |
--------------------------------------------------------------------------------
/neo4j/README.md:
--------------------------------------------------------------------------------
1 | # Neo4j
2 |
3 | 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)
4 |
5 | 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 ui app, try installing and using Neo4j locally instead.
6 |
7 | ## Sandbox setup
8 | A good tutorial can be found here: https://www.youtube.com/watch?v=rPC71lUhK_I
9 |
10 | ## Local setup
11 | 1. [Download Neo4j Desktop](https://neo4j.com/download/)
12 | 2. Install and open Neo4j Desktop.
13 | 3. Create a new DB by clicking "New Graph", and clicking "create local graph".
14 | 4. Set password to "letmein" (as suggested by `api/.env`), and click "Create".
15 | 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`
16 | 6. Click "Manage".
17 | 7. Click "Plugins".
18 | 8. Find "APOC" and click "Install".
19 | 9. Click the "play" button at the top of left the screen, which should start the server. _(screenshot 2)_
20 | 10. Wait until it says "RUNNING".
21 | 11. Proceed forward with the rest of the tutorial.
22 |
23 |
--------------------------------------------------------------------------------
/ui/src/components/Nav.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
50 |
51 |
87 |
--------------------------------------------------------------------------------
/api/src/index.js:
--------------------------------------------------------------------------------
1 | import { typeDefs, resolvers } from "./graphql-schema";
2 | import { ApolloServer } from "apollo-server-express";
3 | import express from "express";
4 | import { v1 as neo4j } from "neo4j-driver";
5 | import { makeAugmentedSchema } from "neo4j-graphql-js";
6 | import dotenv from "dotenv";
7 |
8 | // set environment variables from ../.env
9 | dotenv.config();
10 | const dev = process.env.NODE_ENV === "development";
11 |
12 | const app = express();
13 |
14 | /*
15 | * Create an executable GraphQL schema object from GraphQL type definitions
16 | * including autogenerated queries and mutations.
17 | * Optionally a config object can be included to specify which types to include
18 | * in generated queries and/or mutations. Read more in the docs:
19 | * https://grandstack.io/docs/neo4j-graphql-js-api.html#makeaugmentedschemaoptions-graphqlschema
20 | */
21 |
22 | const schema = makeAugmentedSchema({
23 | typeDefs,
24 | resolvers
25 | });
26 |
27 | /*
28 | * Create a Neo4j driver instance to connect to the database
29 | * using credentials specified as environment variables
30 | */
31 | const driver = neo4j.driver(
32 | process.env.NEO4J_URI,
33 | neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)
34 | );
35 |
36 | /*
37 | * Create a new ApolloServer instance, serving the GraphQL schema
38 | * created using makeAugmentedSchema above and injecting the Neo4j driver
39 | * instance into the context object so it is available in the
40 | * generated resolvers to connect to the database.
41 | */
42 | const server = new ApolloServer({
43 | context: { driver },
44 | schema: schema,
45 | introspection: dev,
46 | playground: dev
47 | });
48 |
49 | // Specify port and path for GraphQL endpoint
50 | const port = process.env.GRAPHQL_LISTEN_PORT || 4001;
51 | const path = "/graphql";
52 |
53 | /*
54 | * Optionally, apply Express middleware for authentication, etc
55 | * This also also allows us to specify a path for the GraphQL endpoint
56 | */
57 | server.applyMiddleware({ app, path });
58 |
59 | app.listen({ port, path }, () => {
60 | console.log(`GraphQL server ready at http://localhost:${port}${path}`);
61 | });
62 |
--------------------------------------------------------------------------------
/test/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 | import { v1 as neo4j } from "neo4j-driver";
27 |
28 | Cypress.Commands.add(
29 | "selectNth",
30 | { prevSubject: "element" },
31 | (subject, pos) => {
32 | cy.wrap(subject)
33 | .children("option")
34 | .eq(pos)
35 | .then((e) => {
36 | cy.wrap(subject).select(e.val());
37 | });
38 | }
39 | );
40 |
41 | Cypress.Commands.add("seedDb", () => {
42 | cy.fixture("seedDb.cypher").then((seedDb) => {
43 | const DELETE_ALL = "MATCH (n) DETACH DELETE n";
44 |
45 | const escapedSeedDb = seedDb.replace(/'/g, "\\'");
46 | const LOAD_SEED_DB = `
47 | CALL apoc.cypher.runMany('
48 | ${escapedSeedDb}
49 | ',
50 | {})
51 | YIELD row, result
52 | RETURN row, result;
53 | `;
54 | console.log("Resetting database");
55 | (async () => {
56 | const driver = neo4j.driver(
57 | Cypress.env("NEO4J_URI"),
58 | neo4j.auth.basic(
59 | Cypress.env("NEO4J_USER"),
60 | Cypress.env("NEO4J_PASSWORD")
61 | )
62 | );
63 | const session = driver.session();
64 | try {
65 | await session.run(DELETE_ALL);
66 | await session.run(LOAD_SEED_DB);
67 | } finally {
68 | await session.close();
69 | await driver.close();
70 | }
71 | })();
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sand-stack-starter-ui",
3 | "version": "0.0.1",
4 | "description": "UI app for SANDstack",
5 | "scripts": {
6 | "dev": "export SAPPER_APP_TIMESTAMP=$(date +%s%3N) && sapper dev",
7 | "build": "export SAPPER_APP_TIMESTAMP=$(date +%s%3N) && sapper build --legacy",
8 | "export": "sapper export --legacy --concurrent=1 --timeout=60000",
9 | "start": "node __sapper__/build",
10 | "cy:run": "cypress run",
11 | "cy:open": "cypress open",
12 | "test": "run-p --race start cy:run"
13 | },
14 | "browserslist": [
15 | ">0.2%",
16 | "not dead",
17 | "not ie <= 11",
18 | "not op_mini all"
19 | ],
20 | "private": true,
21 | "license": "UNLICENSED",
22 | "repository": {
23 | "type": "git",
24 | "url": "https://github.com/vanbenj/HouseParty.git"
25 | },
26 | "dependencies": {
27 | "compression": "^1.7.1",
28 | "polka": "next",
29 | "sirv": "^0.4.0"
30 | },
31 | "devDependencies": {
32 | "@babel/core": "^7.9.0",
33 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
34 | "@babel/plugin-transform-runtime": "^7.9.0",
35 | "@babel/preset-env": "^7.9.0",
36 | "@babel/runtime": "^7.9.2",
37 | "@rollup/plugin-commonjs": "^11.0.0",
38 | "@rollup/plugin-node-resolve": "^7.0.0",
39 | "@rollup/plugin-replace": "^2.3.1",
40 | "apollo-boost": "^0.4.7",
41 | "apollo-cache-inmemory": "^1.6.5",
42 | "apollo-client": "^2.6.8",
43 | "autoprefixer": "^9.7.4",
44 | "devalue": "^2.0.1",
45 | "dotenv": "^8.2.0",
46 | "eslint": "^6.8.0",
47 | "eslint-config-prettier": "^6.10.0",
48 | "eslint-config-standard": "^14.1.1",
49 | "eslint-plugin-import": "^2.20.1",
50 | "eslint-plugin-json": "^1.4.0",
51 | "eslint-plugin-node": "^10.0.0",
52 | "eslint-plugin-prettier": "^3.1.2",
53 | "eslint-plugin-promise": "^4.2.1",
54 | "eslint-plugin-standard": "^4.0.1",
55 | "eslint-plugin-svelte3": "^2.7.3",
56 | "graphql": "^14.6.0",
57 | "node-fetch": "^2.6.0",
58 | "node-sass": "^4.13.1",
59 | "npm-run-all": "^4.1.5",
60 | "prettier": "^1.19.1",
61 | "prettier-plugin-svelte": "^0.7.0",
62 | "rollup": "^1.32.1",
63 | "rollup-plugin-babel": "^4.4.0",
64 | "rollup-plugin-json": "^4.0.0",
65 | "rollup-plugin-svelte": "^5.0.1",
66 | "rollup-plugin-terser": "^5.3.0",
67 | "sapper": "^0.27.11",
68 | "svelte": "^3.20.1",
69 | "sapper-environment": "1.0.1"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/ui/src/service-worker.js:
--------------------------------------------------------------------------------
1 | import { files, shell, routes } from "@sapper/service-worker";
2 | const timestamp = process.env.SAPPER_APP_TIMESTAMP; // instead of `import { timestamp }`
3 |
4 | const ASSETS = `cache${timestamp}`;
5 |
6 | // `shell` is an array of all the files generated by the bundler,
7 | // `files` is an array of everything in the `static` directory
8 | const to_cache = shell.concat(files);
9 | const cached = new Set(to_cache);
10 |
11 | self.addEventListener("install", (event) => {
12 | event.waitUntil(
13 | caches
14 | .open(ASSETS)
15 | .then((cache) => cache.addAll(to_cache))
16 | .then(() => {
17 | self.skipWaiting();
18 | })
19 | );
20 | });
21 |
22 | self.addEventListener("activate", (event) => {
23 | event.waitUntil(
24 | caches.keys().then(async (keys) => {
25 | // delete old caches
26 | for (const key of keys) {
27 | if (key !== ASSETS) await caches.delete(key);
28 | }
29 |
30 | self.clients.claim();
31 | })
32 | );
33 | });
34 |
35 | self.addEventListener("fetch", (event) => {
36 | if (event.request.method !== "GET" || event.request.headers.has("range"))
37 | return;
38 |
39 | const url = new URL(event.request.url);
40 |
41 | // don't try to handle e.g. data: URIs
42 | if (!url.protocol.startsWith("http")) return;
43 |
44 | // ignore dev server requests
45 | if (
46 | url.hostname === self.location.hostname &&
47 | url.port !== self.location.port
48 | )
49 | return;
50 |
51 | // always serve static files and bundler-generated assets from cache
52 | if (url.host === self.location.host && cached.has(url.pathname)) {
53 | event.respondWith(caches.match(event.request));
54 | return;
55 | }
56 |
57 | // for pages, you might want to serve a shell `service-worker-index.html` file,
58 | // which Sapper has generated for you. It's not right for every
59 | // app, but if it's right for yours then uncomment this section
60 | /*
61 | if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
62 | event.respondWith(caches.match('/service-worker-index.html'));
63 | return;
64 | }
65 | */
66 |
67 | if (event.request.cache === "only-if-cached") return;
68 |
69 | // for everything else, try the network first, falling back to
70 | // cache if the user is offline. (If the pages never change, you
71 | // might prefer a cache-first approach to a network-first one.)
72 | event.respondWith(
73 | caches.open(`offline${timestamp}`).then(async (cache) => {
74 | try {
75 | const response = await fetch(event.request);
76 | cache.put(event.request, response.clone());
77 | return response;
78 | } catch (err) {
79 | const response = await cache.match(event.request);
80 | if (response) return response;
81 |
82 | throw err;
83 | }
84 | })
85 | );
86 | });
87 |
--------------------------------------------------------------------------------
/ui/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from "@rollup/plugin-node-resolve";
2 | import replace from "@rollup/plugin-replace";
3 | import commonjs from "@rollup/plugin-commonjs";
4 | import svelte from "rollup-plugin-svelte";
5 | import babel from "rollup-plugin-babel";
6 | import { terser } from "rollup-plugin-terser";
7 | import config from "sapper/config/rollup.js";
8 | import pkg from "./package.json";
9 | import sapperEnv from "sapper-environment";
10 |
11 | const mode = process.env.NODE_ENV;
12 | const dev = mode === "development";
13 | const legacy = !!process.env.SAPPER_LEGACY_BUILD;
14 |
15 | const onwarn = (warning, onwarn) =>
16 | (warning.code === "CIRCULAR_DEPENDENCY" &&
17 | /[/\\]@sapper[/\\]/.test(warning.message)) ||
18 | onwarn(warning);
19 |
20 | export default {
21 | client: {
22 | input: config.client.input(),
23 | output: config.client.output(),
24 | plugins: [
25 | replace({
26 | ...sapperEnv(),
27 | "process.browser": true,
28 | "process.env.NODE_ENV": JSON.stringify(mode)
29 | }),
30 | svelte({
31 | dev,
32 | hydratable: true,
33 | emitCss: true
34 | }),
35 | resolve({
36 | browser: true,
37 | dedupe: ["svelte"]
38 | }),
39 | commonjs(),
40 |
41 | legacy &&
42 | babel({
43 | extensions: [".js", ".mjs", ".html", ".svelte"],
44 | runtimeHelpers: true,
45 | exclude: ["node_modules/@babel/**"],
46 | presets: [
47 | [
48 | "@babel/preset-env",
49 | {
50 | targets: "> 0.25%, not dead"
51 | }
52 | ]
53 | ],
54 | plugins: [
55 | "@babel/plugin-syntax-dynamic-import",
56 | [
57 | "@babel/plugin-transform-runtime",
58 | {
59 | useESModules: true
60 | }
61 | ]
62 | ]
63 | }),
64 |
65 | !dev &&
66 | terser({
67 | module: true
68 | })
69 | ],
70 |
71 | onwarn
72 | },
73 |
74 | server: {
75 | input: config.server.input(),
76 | output: config.server.output(),
77 | plugins: [
78 | replace({
79 | ...sapperEnv(),
80 | "process.browser": false,
81 | "process.env.NODE_ENV": JSON.stringify(mode)
82 | }),
83 | svelte({
84 | generate: "ssr",
85 | dev
86 | }),
87 | resolve({
88 | dedupe: ["svelte"]
89 | }),
90 | commonjs()
91 | ],
92 | external: Object.keys(pkg.dependencies).concat(
93 | require("module").builtinModules ||
94 | Object.keys(process.binding("natives"))
95 | ),
96 |
97 | onwarn
98 | },
99 |
100 | serviceworker: {
101 | input: config.serviceworker.input(),
102 | output: config.serviceworker.output(),
103 | plugins: [
104 | resolve(),
105 | replace({
106 | ...sapperEnv(),
107 | "process.browser": true,
108 | "process.env.NODE_ENV": JSON.stringify(mode)
109 | }),
110 | commonjs(),
111 | !dev && terser()
112 | ],
113 |
114 | onwarn
115 | }
116 | };
117 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | # sapper-template
2 |
3 | This component is based on the default [Sapper template project](https://github.com/sveltejs/sapper-template).
4 |
5 |
6 | ## Getting started
7 |
8 | ### Running the project
9 |
10 | However you get the code, you can install dependencies and run the project in development mode with:
11 |
12 | ```bash
13 | cd ui
14 | npm install # or yarn
15 | npm run dev
16 | ```
17 |
18 | Open up [localhost:3000](http://localhost:3000) and start clicking around.
19 |
20 | Consult [sapper.svelte.dev](https://sapper.svelte.dev) for help getting started.
21 |
22 |
23 | ## Structure
24 |
25 | Sapper expects to find two directories in the root of your project — `src` and `static`.
26 |
27 |
28 | ### src
29 |
30 | The [src](src) directory contains the entry points for your app — `client.js`, `server.js` and (optionally) a `service-worker.js` — along with a `template.html` file and a `routes` directory.
31 |
32 |
33 | #### src/routes
34 |
35 | This is the heart of your Sapper app. There are two kinds of routes — *pages*, and *server routes*.
36 |
37 | **Pages** are Svelte components written in `.svelte` files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel. (Sapper will preload and cache the code for these subsequent pages, so that navigation is instantaneous.)
38 |
39 | **Server routes** are modules written in `.js` files, that export functions corresponding to HTTP methods. Each function receives Express `request` and `response` objects as arguments, plus a `next` function. This is useful for creating a JSON API, for example.
40 |
41 | There are three simple rules for naming the files that define your routes:
42 |
43 | * A file called `src/routes/about.svelte` corresponds to the `/about` route. A file called `src/routes/blog/[slug].svelte` corresponds to the `/blog/:slug` route, in which case `params.slug` is available to the route
44 | * The file `src/routes/index.svelte` (or `src/routes/index.js`) corresponds to the root of your app. `src/routes/about/index.svelte` is treated the same as `src/routes/about.svelte`.
45 | * Files and directories with a leading underscore do *not* create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called `src/routes/_helpers/datetime.js` and it would *not* create a `/_helpers/datetime` route
46 |
47 |
48 | ### static
49 |
50 | The [static](static) directory contains any static assets that should be available. These are served using [sirv](https://github.com/lukeed/sirv).
51 |
52 | In your [service-worker.js](src/service-worker.js) file, you can import these as `files` from the generated manifest...
53 |
54 | ```js
55 | import { files } from '@sapper/service-worker';
56 | ```
57 |
58 | ...so that you can cache them (though you can choose not to, for example if you don't want to cache very large files).
59 |
60 |
61 | ## Bundler config
62 |
63 | Sapper uses Rollup or webpack to provide code-splitting and dynamic imports, as well as compiling your Svelte components. With webpack, it also provides hot module reloading. As long as you don't do anything daft, you can edit the configuration files to add whatever plugins you'd like.
64 |
65 |
66 | ## Production mode and deployment
67 |
68 | To start a production version of your app, run `npm run build && npm start`. This will disable live reloading, and activate the appropriate bundler plugins.
69 |
70 | You can deploy your application to any environment that supports Node 8 or above. As an example, to deploy to [Now](https://zeit.co/now), run these commands:
71 |
72 | ```bash
73 | npm install -g now
74 | now
75 | ```
76 |
77 |
78 | ## Using external components
79 |
80 | When using Svelte components installed from npm, such as [@sveltejs/svelte-virtual-list](https://github.com/sveltejs/svelte-virtual-list), Svelte needs the original component source (rather than any precompiled JavaScript that ships with the component). This allows the component to be rendered server-side, and also keeps your client-side app smaller.
81 |
82 | Because of that, it's essential that the bundler doesn't treat the package as an *external dependency*. You can either modify the `external` option under `server` in [rollup.config.js](rollup.config.js) or the `externals` option in [webpack.config.js](webpack.config.js), or simply install the package to `devDependencies` rather than `dependencies`, which will cause it to get bundled (and therefore compiled) with your app:
83 |
84 | ```bash
85 | npm install -D @sveltejs/svelte-virtual-list
86 | ```
87 |
88 |
89 | ## Bugs and feedback
90 |
91 | Sapper is in early development, and may have the odd rough edge here and there. Please be vocal over on the [Sapper issue tracker](https://github.com/sveltejs/sapper/issues).
--------------------------------------------------------------------------------
/test/cypress/fixtures/seedDb.cypher:
--------------------------------------------------------------------------------
1 |
2 | // this script was generated using
3 | // https://neo4j.com/docs/labs/apoc/current/export/cypher/#export-cypher-neo4j-browser
4 | // you can run the command using http://localhost:7474/browser/
5 | // after the command is run you can use the docker shell
6 | // docker exec -it sand-stack-starter_neo4j_1 sh
7 | // to find the file in the import folder on the neo4j server
8 | CREATE CONSTRAINT ON (node:`UNIQUE IMPORT LABEL`) ASSERT (node.`UNIQUE IMPORT ID`) IS UNIQUE;
9 | UNWIND [{_id:0, properties:{name:"Will", id:"u1"}}, {_id:1, properties:{name:"Bob", id:"u2"}}, {_id:2, properties:{name:"Jenny", id:"u3"}}, {_id:3, properties:{name:"Angie", id:"u4"}}] AS row
10 | CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:User;
11 | UNWIND [{_id:21, properties:{name:"Coffee"}}, {_id:22, properties:{name:"Library"}}, {_id:23, properties:{name:"Beer"}}, {_id:24, properties:{name:"Restaurant"}}, {_id:25, properties:{name:"Ramen"}}, {_id:26, properties:{name:"Cafe"}}, {_id:27, properties:{name:"Deli"}}, {_id:28, properties:{name:"Breakfast"}}, {_id:29, properties:{name:"Brewery"}}] AS row
12 | CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Category;
13 | UNWIND [{_id:30, properties:{date:date('2016-01-03'), text:"Great IPA selection!", id:"r1", stars:4}}, {_id:31, properties:{date:date('2016-07-14'), text:"I love everything", id:"r2", stars:5}}, {_id:32, properties:{date:date('2018-09-10'), text:"It's lacking something", id:"r3", stars:3}}, {_id:33, properties:{date:date('2017-11-13'), text:"Love it more", id:"r4", stars:5}}, {_id:34, properties:{date:date('2018-01-03'), text:"Best breakfast sandwich at the Farmer's Market. Always get the works.", id:"r5", stars:4}}, {_id:35, properties:{date:date('2018-03-24'), text:"Uses mostly organic food, vegan friendly", id:"r6", stars:4}}, {_id:36, properties:{date:date('2015-08-29'), text:"Not a great selection of books, but fortunately the inter-library loan system is good. Wifi is quite slow.", id:"r7", stars:3}}, {_id:37, properties:{date:date('2018-08-11'), text:"Zebra pale ale is nice", id:"r8", stars:5}}, {_id:38, properties:{date:date('2016-11-21'), text:"Love it!!", id:"r9", stars:5}}, {_id:39, properties:{date:date('2015-12-15'), text:"Somewhat lacking in imagination", id:"r10", stars:2}}] AS row
14 | CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Review;
15 | UNWIND [{_id:4, properties:{address:"313 N 1st St W", city:"Missoula", name:"KettleHouse Brewing Co.", state:"MT", id:"b1"}}, {_id:5, properties:{address:"1151 W Broadway St", city:"Missoula", name:"Imagine Nation Brewing", state:"MT", id:"b2"}}, {_id:6, properties:{address:"Food Truck - Farmers Market", city:"Missoula", name:"Ninja Mike's", id:"b3", state:"MT"}}, {_id:7, properties:{address:"201 E Front St", city:"Missoula", name:"Market on Front", state:"MT", id:"b4"}}, {_id:8, properties:{address:"301 E Main St", city:"Missoula", name:"Missoula Public Library", state:"MT", id:"b5"}}, {_id:9, properties:{address:"121 W Broadway St", city:"Missoula", name:"Zootown Brew", id:"b6", state:"MT"}}, {_id:10, properties:{address:"723 California Dr", city:"Burlingame", name:"Hanabi", id:"b7", state:"CA"}}, {_id:11, properties:{address:"113 B St", city:"San Mateo", name:"Philz Coffee", id:"b8", state:"CA"}}, {_id:12, properties:{address:"121 Industrial Rd #11", city:"Belmont", name:"Alpha Acid Brewing Company", id:"b9", state:"CA"}}, {_id:20, properties:{address:"55 W 3rd Ave", city:"San Mateo", name:"San Mateo Public Library Central Library", state:"CA", id:"b10"}}] AS row
16 | CREATE (n:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row._id}) SET n += row.properties SET n:Business;
17 | UNWIND [{start: {_id:0}, end: {_id:30}, properties:{}}, {start: {_id:2}, end: {_id:31}, properties:{}}, {start: {_id:3}, end: {_id:32}, properties:{}}, {start: {_id:2}, end: {_id:33}, properties:{}}, {start: {_id:0}, end: {_id:34}, properties:{}}, {start: {_id:1}, end: {_id:35}, properties:{}}, {start: {_id:0}, end: {_id:36}, properties:{}}, {start: {_id:3}, end: {_id:37}, properties:{}}, {start: {_id:2}, end: {_id:38}, properties:{}}, {start: {_id:1}, end: {_id:39}, properties:{}}] AS row
18 | MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
19 | MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
20 | CREATE (start)-[r:WROTE]->(end) SET r += row.properties;
21 | UNWIND [{start: {_id:4}, end: {_id:29}, properties:{}}, {start: {_id:5}, end: {_id:23}, properties:{}}, {start: {_id:5}, end: {_id:29}, properties:{}}, {start: {_id:6}, end: {_id:24}, properties:{}}, {start: {_id:6}, end: {_id:28}, properties:{}}, {start: {_id:7}, end: {_id:21}, properties:{}}, {start: {_id:7}, end: {_id:24}, properties:{}}, {start: {_id:7}, end: {_id:26}, properties:{}}, {start: {_id:7}, end: {_id:27}, properties:{}}, {start: {_id:7}, end: {_id:28}, properties:{}}, {start: {_id:8}, end: {_id:22}, properties:{}}, {start: {_id:9}, end: {_id:21}, properties:{}}, {start: {_id:10}, end: {_id:24}, properties:{}}, {start: {_id:10}, end: {_id:25}, properties:{}}, {start: {_id:11}, end: {_id:21}, properties:{}}, {start: {_id:11}, end: {_id:28}, properties:{}}, {start: {_id:12}, end: {_id:29}, properties:{}}, {start: {_id:20}, end: {_id:22}, properties:{}}, {start: {_id:4}, end: {_id:23}, properties:{}}] AS row
22 | MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
23 | MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
24 | CREATE (start)-[r:IN_CATEGORY]->(end) SET r += row.properties;
25 | UNWIND [{start: {_id:30}, end: {_id:4}, properties:{}}, {start: {_id:31}, end: {_id:4}, properties:{}}, {start: {_id:32}, end: {_id:5}, properties:{}}, {start: {_id:33}, end: {_id:6}, properties:{}}, {start: {_id:34}, end: {_id:6}, properties:{}}, {start: {_id:35}, end: {_id:7}, properties:{}}, {start: {_id:36}, end: {_id:8}, properties:{}}, {start: {_id:37}, end: {_id:9}, properties:{}}, {start: {_id:38}, end: {_id:10}, properties:{}}, {start: {_id:39}, end: {_id:5}, properties:{}}] AS row
26 | MATCH (start:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.start._id})
27 | MATCH (end:`UNIQUE IMPORT LABEL`{`UNIQUE IMPORT ID`: row.end._id})
28 | CREATE (start)-[r:REVIEWS]->(end) SET r += row.properties;
29 | MATCH (n:`UNIQUE IMPORT LABEL`) WITH n LIMIT 20000 REMOVE n:`UNIQUE IMPORT LABEL` REMOVE n.`UNIQUE IMPORT ID`;
30 | DROP CONSTRAINT ON (node:`UNIQUE IMPORT LABEL`) ASSERT (node.`UNIQUE IMPORT ID`) IS UNIQUE;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SANDstack Starter
2 |
3 | This project is a starter for building a SANDstack ([Sveltejs](https://svelte.dev/)/[Sapper](https://sapper.svelte.dev/), [Apollo GraphQL](https://www.apollographql.com/), [Neo4j Database](https://neo4j.com/neo4j-graph-database/)) application. There are two components to the starter, the UI application (a Svelte/Sapper app) and the API app (GraphQL server).
4 |
5 | This project used as a starting point the api component from the [GRANDstack](https://grandstack.io) project and the default [Sapper template](https://github.com/sveltejs/sapper-template).
6 |
7 | The master branch **DOES NOT USE** [Svelte Apollo](https://github.com/timhall/svelte-apollo) There is a separate branch that demonstrates it's use.
8 |
9 | If you are new to Svelete this is a good [introductory video](https://youtu.be/AdNJ3fydeao)
10 |
11 | The GRANDStack documentation provides a good overview of the [Neo4j GraphQL](https://grandstack.io/docs/neo4j-graphql-overview.html) integration.
12 |
13 | ## Setting up a development environment
14 |
15 | Follow [these instructions first](./README-DEV.md)
16 |
17 | ## Quickstart
18 |
19 | You can quickly start using Docker engine version 19 or later:
20 |
21 | ```
22 | docker-compose -f docker-compose.yml -f docker-compose-stage.yml up --build
23 | ```
24 |
25 | List the running containers:
26 |
27 | ```
28 | docker ps
29 | ```
30 |
31 | There should be three containers started:
32 |
33 | ```
34 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
35 | 625acfa444fa sand-stack-starter_ui "docker-entrypoint.s…" 3 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp sand-stack-starter_ui_1
36 | 843c5ea8d997 sand-stack-starter_api "docker-entrypoint.s…" 3 minutes ago Up 2 minutes 0.0.0.0:4001->4001/tcp sand-stack-starter_api_1
37 | 8ae79170f66e sand-stack-starter_neo4j "/sbin/tini -g -- /d…" 3 minutes ago Up 3 minutes 0.0.0.0:7474->7474/tcp, 7473/tcp, 0.0.0.0:7687->7687/tcp sand-stack-starter_neo4j_1
38 | ```
39 |
40 | Initially the database is empty. Running the test suite automatically loads the database with sample data.
41 |
42 | Open a new terminal
43 |
44 | ```
45 | cd test
46 | npm install
47 | npx cypress run
48 | ```
49 |
50 | The application should be running at [localhost:3000](http://localhost:3000)
51 | 
52 |
53 | ## Development mode
54 |
55 | The project is set up to allow the api and ui servers to run in dev mode. This mode enables a watch on all files and automatically deploys when changes are saved. The following instructions show you how to start each server component separately.
56 |
57 | If you've run the docker-compose script be sure to shut down the docker containers before starting in development mode.
58 |
59 | ```
60 | docker-compose down
61 | ```
62 |
63 | Be sure your development machine is running Node version 12.
64 |
65 | ```
66 | node --version
67 | ```
68 |
69 | ### Neo4j
70 |
71 | There are many ways to run Neo4j for development purposes. The [README](neo4j/README.md) in the `neo4j` directory describes several approaches. However the simplest is to start a stand alone Docker container using the DockerFile provided.
72 |
73 | ```
74 | docker-compose up --detach --remove-orphans
75 | ```
76 |
77 | List the running containers:
78 |
79 | ```
80 | docker ps
81 | ```
82 |
83 | You should see a neo4j container:
84 |
85 | ```
86 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
87 | 3c95a2947d49 neo4j "/sbin/tini -g -- /d…" 9 seconds ago Up 8 seconds 0.0.0.0:7474->7474/tcp, 7473/tcp, 0.0.0.0:7687->7687/tcp neo4j-db
88 | ```
89 |
90 | You should now be able to access the Neo4j database browser at [localhost:7474](http://localhost:7474) (you can log in using neo4j/letmein). Click the `*()` under `Node Labels` to list a sample of nodes.
91 | 
92 |
93 | ### Install dependencies
94 |
95 | ```
96 | (cd ../ui && npm install)
97 | (cd ../api && npm install)
98 | ```
99 |
100 | ### Start API Server
101 |
102 | Open a new terminal
103 |
104 | ```
105 | cd api
106 | npm run dev
107 | ```
108 |
109 | This will start the GraphQL API server in dev mode.
110 |
111 | You should also be able to access the Apollo GraphQL playground at [localhost:4001/graphql](http://localhost:4001/graphql) This allows you to interactively test your GraphQL before using it in your application.
112 | 
113 |
114 | ### Start UI Server
115 |
116 | Open a new terminal
117 |
118 | ```
119 | cd ui
120 | npm run dev
121 | ```
122 |
123 | This will start the Svelte Sapper app in dev mode. In dev mode when a file change is saved in the `ui` directory the web page should automatically reload with the changes.
124 |
125 | ### Start Cypress Test Server
126 |
127 | Open a new terminal
128 |
129 | ```
130 | cd test
131 | npx cypress open
132 | ```
133 |
134 | The test runner lets you select the browser you wish to use for testing.
135 | 
136 |
137 | We've found as as apps get larger and more complex it is often necessay to add [cy.wait(time)](https://docs.cypress.io/api/commands/wait.html#Syntax) to make the test stable on slower CI machines. These are end to end tests so many factors can affect test performance.
138 |
139 | ### Github Continuous Integration
140 |
141 | The `/.github/workflows/test.yml` is a Github action that runs the Cypress end to end tests and stores the test results to github on every push.
142 |
143 | ### Sapper Export
144 |
145 | This simple example is a readonly app and a candidate for [Sapper Export](https://sapper.svelte.dev/docs#Exporting)
146 |
147 | For the export to run properly the `neo4j` and `api` servers must be running.
148 |
149 | Open a new terminal
150 |
151 | ```
152 | cd ui
153 | npx sapper export
154 | ```
155 |
156 | This will create a `/ui/__sapper__/export` folder with a production-ready build of your site. You can launch it like so:
157 |
158 | ```
159 | npx serve __sapper__/export
160 | ```
161 |
162 | ### Sapper Environment
163 |
164 | The `ui` app makes use of the [Sapper Environment npm](https://www.npmjs.com/package/sapper-environment). These are environment variables that are replaced at build time by `rollup.config.js` so they can be used in client code. Any environment variable with prefix `SAPPER_APP_` is for use in the client code.
165 |
166 | We make especial use of this in `/ui/src/apollo.js`. The Apollo GraphQl client is used in both the browser and SSR. In dev mode they have the same value.
167 |
168 | ```
169 | SSR_GRAPHQL_URI=http://localhost:4001/graphql
170 | SAPPER_APP_GRAPHQL_URI=http://localhost:4001/graphql
171 | ```
172 |
173 | However in production they are often different as the `SAPPER_APP_GRAPHQL_URI` is the public address of the api server and `SSR_GRAPHQL_URI` is the internal address of the server. An example of this can be seen in `docker-compose-stage.yml`.
174 |
--------------------------------------------------------------------------------