├── api
├── .dockerignore
├── Dockerfile
├── src
│ ├── graphql-schema.js
│ ├── functions
│ │ └── graphql
│ │ │ ├── package.json
│ │ │ └── graphql.js
│ ├── seed
│ │ ├── seed-db.js
│ │ └── seed-mutations.js
│ ├── index.js
│ └── schema.graphql
├── .env
├── babel.config.js
├── package.json
├── README.md
└── auth0-howto.md
├── web-react
├── .dockerignore
├── src
│ ├── index.css
│ ├── setupProxy.js
│ ├── components
│ │ ├── Title.js
│ │ ├── UserCount.js
│ │ ├── RatingsChart.js
│ │ ├── Dashboard.js
│ │ ├── RecentReviews.js
│ │ └── UserList.js
│ ├── App.test.js
│ ├── index.js
│ ├── registerServiceWorker.js
│ └── App.js
├── .env
├── public
│ ├── favicon.ico
│ ├── img
│ │ └── grandstack.png
│ ├── manifest.json
│ └── index.html
├── img
│ └── default-app.png
├── Dockerfile
└── package.json
├── web-react-ts
├── .dockerignore
├── src
│ ├── react-app-env.d.ts
│ ├── setupTests.ts
│ ├── components
│ │ ├── Title.tsx
│ │ ├── UserCount.tsx
│ │ ├── RatingsChart.tsx
│ │ ├── Dashboard.tsx
│ │ ├── RecentReviews.tsx
│ │ └── UserList.tsx
│ ├── index.css
│ ├── App.test.tsx
│ ├── index.tsx
│ ├── logo.svg
│ └── serviceWorker.ts
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── img
│ │ └── grandstack.png
│ ├── manifest.json
│ └── index.html
├── img
│ └── default-app.png
├── Dockerfile
├── .gitignore
├── tsconfig.json
├── package.json
└── README.md
├── mobile_client_flutter
├── ios
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-50x50@1x.png
│ │ │ │ ├── Icon-App-50x50@2x.png
│ │ │ │ ├── Icon-App-57x57@1x.png
│ │ │ │ ├── Icon-App-57x57@2x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-72x72@1x.png
│ │ │ │ ├── Icon-App-72x72@2x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ └── .gitignore
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ └── Icon-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── launcher_icon.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── launcher_icon.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── launcher_icon.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── launcher_icon.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ └── launcher_icon.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ └── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── io
│ │ │ │ │ │ └── grandstack
│ │ │ │ │ │ └── client_flutter
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── assets
│ ├── images
│ │ └── grandstack.png
│ └── icons
│ │ └── grandstack_icon.png
├── .metadata
├── lib
│ ├── services
│ │ └── gql.dart
│ ├── widgets
│ │ ├── rating_display.dart
│ │ ├── business_list_tile.dart
│ │ ├── menu_drawer.dart
│ │ └── alert_box.dart
│ ├── main.dart
│ ├── screens
│ │ ├── business_list_screen.dart
│ │ ├── user_list_screen.dart
│ │ └── business_detail_screen.dart
│ └── model
│ │ ├── model.dart
│ │ └── model.g.dart
├── pubspec.yaml
├── .gitignore
└── README.md
├── scripts
├── config
│ └── index.json
├── seed.js
├── README.md
├── common.js
├── start-dev.js
├── build.js
└── inferSchema.js
├── .prettierrc.json
├── img
├── apoc-install.png
├── grandstack-app.png
├── neo4j-sandbox.png
├── desktop-new-graph.png
├── grandstack-flutter.png
├── graphql-playground.png
└── create-grandstack-app.png
├── web-angular
├── src
│ ├── styles.scss
│ ├── favicon.ico
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── assets
│ │ └── GrandStack-Logo.png
│ ├── app
│ │ ├── app.component.ts
│ │ ├── app-routing.module.ts
│ │ ├── types.ts
│ │ ├── app.component.html
│ │ ├── app.module.ts
│ │ ├── graphql
│ │ │ └── graphql.module.ts
│ │ ├── users
│ │ │ ├── users.component.html
│ │ │ └── users.component.ts
│ │ ├── businesses
│ │ │ ├── businesses.component.html
│ │ │ └── businesses.component.ts
│ │ └── utils
│ │ │ └── query-helpers.ts
│ ├── index.html
│ ├── main.ts
│ └── polyfills.ts
├── img
│ └── angular-ui.jpg
├── proxy.conf.json
├── tsconfig.app.json
├── browserslist
├── tsconfig.json
├── .gitignore
├── README.md
├── package.json
└── angular.json
├── .vscode
└── settings.json
├── .gitignore
├── app.json
├── neo4j
├── Dockerfile
└── README.md
├── .eslintrc.json
├── netlify.toml
├── docker-compose.yml
├── package.json
├── vercel.json
├── CONTRIBUTING.md
└── CODE_OF_CONDUCT.md
/api/.dockerignore:
--------------------------------------------------------------------------------
1 | *~
2 |
--------------------------------------------------------------------------------
/web-react/.dockerignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/web-react-ts/.dockerignore:
--------------------------------------------------------------------------------
1 | .env
2 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/web-react-ts/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/scripts/config/index.json:
--------------------------------------------------------------------------------
1 | { "templateFileName": "web-react", "templateName": "React" }
2 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "semi": false,
4 | "singleQuote": true
5 | }
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/img/apoc-install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/apoc-install.png
--------------------------------------------------------------------------------
/web-react/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/img/grandstack-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/grandstack-app.png
--------------------------------------------------------------------------------
/img/neo4j-sandbox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/neo4j-sandbox.png
--------------------------------------------------------------------------------
/web-angular/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/web-react-ts/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/img/desktop-new-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/desktop-new-graph.png
--------------------------------------------------------------------------------
/img/grandstack-flutter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/grandstack-flutter.png
--------------------------------------------------------------------------------
/img/graphql-playground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/graphql-playground.png
--------------------------------------------------------------------------------
/web-angular/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-angular/src/favicon.ico
--------------------------------------------------------------------------------
/web-react/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_GRAPHQL_URI=/graphql
2 | PROXY=http://localhost:4001/graphql
3 |
4 | SKIP_PREFLIGHT_CHECK=true
--------------------------------------------------------------------------------
/web-react/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-react/public/favicon.ico
--------------------------------------------------------------------------------
/img/create-grandstack-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/img/create-grandstack-app.png
--------------------------------------------------------------------------------
/web-angular/img/angular-ui.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-angular/img/angular-ui.jpg
--------------------------------------------------------------------------------
/web-react/img/default-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-react/img/default-app.png
--------------------------------------------------------------------------------
/web-react-ts/img/default-app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-react-ts/img/default-app.png
--------------------------------------------------------------------------------
/web-react-ts/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-react-ts/public/favicon.ico
--------------------------------------------------------------------------------
/web-react/public/img/grandstack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-react/public/img/grandstack.png
--------------------------------------------------------------------------------
/mobile_client_flutter/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/web/favicon.png
--------------------------------------------------------------------------------
/web-angular/proxy.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "/graphql": {
3 | "target": "http://localhost:4001/graphql",
4 | "secure": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/web-react-ts/public/img/grandstack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-react-ts/public/img/grandstack.png
--------------------------------------------------------------------------------
/web-angular/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | graphqlServer: '/graphql'
4 | };
5 |
--------------------------------------------------------------------------------
/web-angular/src/assets/GrandStack-Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/web-angular/src/assets/GrandStack-Logo.png
--------------------------------------------------------------------------------
/mobile_client_flutter/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/mobile_client_flutter/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/mobile_client_flutter/assets/images/grandstack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/assets/images/grandstack.png
--------------------------------------------------------------------------------
/mobile_client_flutter/assets/icons/grandstack_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/assets/icons/grandstack_icon.png
--------------------------------------------------------------------------------
/api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 |
3 | RUN mkdir -p /app
4 | WORKDIR /app
5 |
6 | COPY package.json .
7 | RUN npm install
8 | COPY . .
9 |
10 | EXPOSE 4001
11 |
12 | CMD ["npm", "start"]
13 |
--------------------------------------------------------------------------------
/web-react/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 |
3 | RUN mkdir -p /app
4 | WORKDIR /app
5 |
6 | COPY package.json .
7 | RUN npm install
8 | COPY . .
9 |
10 | EXPOSE 3000
11 |
12 | CMD ["npm", "start"]
13 |
--------------------------------------------------------------------------------
/web-react-ts/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12
2 |
3 | RUN mkdir -p /app
4 | WORKDIR /app
5 |
6 | COPY package.json .
7 | RUN npm install
8 | COPY . .
9 |
10 | EXPOSE 3000
11 |
12 | CMD ["npm", "start"]
13 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
--------------------------------------------------------------------------------
/web-angular/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styles: []
7 | })
8 | export class AppComponent {}
9 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/kotlin/io/grandstack/client_flutter/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.grandstack.client_flutter
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grand-stack/grand-stack-starter/HEAD/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/web-react/src/setupProxy.js:
--------------------------------------------------------------------------------
1 | const proxy = require('http-proxy-middleware')
2 |
3 | module.exports = function (app) {
4 | app.use(
5 | '/graphql',
6 | proxy({
7 | target: `${process.env.PROXY}`,
8 | changeOrigin: true,
9 | })
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/web-react-ts/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "git.ignoreLimitWarning": true,
3 | "editor.formatOnSave": true,
4 | "prettier.prettierPath": "node_modules/prettier/index.js",
5 | "editor.codeActionsOnSave": {
6 | "source.fixAll": true
7 | },
8 | "editor.defaultFormatter": "esbenp.prettier-vscode"
9 | }
10 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
7 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/mobile_client_flutter/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 916c3ac648aa0498a70f32b5fc4f6c51447628e3
8 | channel: beta
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/web-angular/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/app",
5 | "types": []
6 | },
7 | "files": [
8 | "src/main.ts",
9 | "src/polyfills.ts"
10 | ],
11 | "include": [
12 | "src/**/*.ts"
13 | ],
14 | "exclude": [
15 | "src/test.ts",
16 | "src/**/*.spec.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/web-angular/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | GRANDstack Angular UI
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/scripts/seed.js:
--------------------------------------------------------------------------------
1 | const concurrently = require('concurrently')
2 | const { API_DIR, runner, concurrentOpts } = require('./common')
3 |
4 | const jobs = [
5 | {
6 | name: 'api:seedDb',
7 | command: `cd ${API_DIR} && ${runner} run seedDb`,
8 | prefixColor: 'yellow',
9 | },
10 | ]
11 |
12 | concurrently(jobs, concurrentOpts).catch((e) => {
13 | console.error(e.message)
14 | })
15 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/web-react/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "GRANDstack App",
3 | "name": "GRANDstack Starter Project",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/api/src/graphql-schema.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 |
4 | /*
5 | * Check for GRAPHQL_SCHEMA environment variable to specify schema file
6 | * fallback to schema.graphql if GRAPHQL_SCHEMA environment variable is not set
7 | */
8 |
9 | export const typeDefs = fs
10 | .readFileSync(
11 | process.env.GRAPHQL_SCHEMA || path.join(__dirname, 'schema.graphql')
12 | )
13 | .toString('utf-8')
14 |
--------------------------------------------------------------------------------
/web-react-ts/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "GRANDstack App",
3 | "name": "GRANDstack Starter Project",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | build/
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 | yarn.lock
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 | *~
24 | *.orig
25 |
--------------------------------------------------------------------------------
/web-react/src/components/Title.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Typography from '@material-ui/core/Typography'
4 |
5 | export default function Title(props) {
6 | return (
7 |
8 | {props.children}
9 |
10 | )
11 | }
12 |
13 | Title.propTypes = {
14 | children: PropTypes.node,
15 | }
16 |
--------------------------------------------------------------------------------
/web-react-ts/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/web-angular/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/web-react-ts/src/components/Title.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Typography from '@material-ui/core/Typography'
4 |
5 | export default function Title(props: any) {
6 | return (
7 |
8 | {props.children}
9 |
10 | )
11 | }
12 |
13 | Title.propTypes = {
14 | children: PropTypes.node,
15 | }
16 |
--------------------------------------------------------------------------------
/web-react-ts/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/services/gql.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/foundation.dart';
2 | import 'dart:io' show Platform;
3 | import 'package:graphql_flutter/graphql_flutter.dart';
4 |
5 | final String host =
6 | kIsWeb ? 'localhost' : Platform.isAndroid ? '10.0.2.2' : 'localhost';
7 |
8 | ValueNotifier client = ValueNotifier(
9 | GraphQLClient(
10 | cache: InMemoryCache(),
11 | link: HttpLink(uri: 'http://$host:4001/graphql'),
12 | ),
13 | );
14 |
--------------------------------------------------------------------------------
/web-angular/browserslist:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # You can see what browsers were selected by your queries by running:
6 | # npx browserslist
7 |
8 | > 0.5%
9 | last 2 versions
10 | Firefox ESR
11 | not dead
12 | not IE 9-11 # For IE 9-11 support, remove 'not'.
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/api/src/functions/graphql/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grand-stack-starter-api-graphql-serverless",
3 | "version": "0.0.1",
4 | "description": "netlify functions:create - set up for apollo graphql",
5 | "main": "graphql.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "dependencies": {
10 | "apollo-server-lambda": "^2.14.2",
11 | "@neo4j/graphql": "^1.0.0-beta.2",
12 | "graphql": "^15.5.0",
13 | "neo4j-driver": "^4.0.2"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/api/.env:
--------------------------------------------------------------------------------
1 | # Use this file to set environment variables with credentials and configuration options
2 | # This file is provided as an example and should be replaced with your own values
3 | # You probably don't want to check this into version control!
4 |
5 | NEO4J_URI=bolt://localhost:7687
6 | NEO4J_USER=neo4j
7 | NEO4J_PASSWORD=letmein
8 |
9 | # Uncomment this line to specify a specific Neo4j database (v4.x+ only)
10 | #NEO4J_DATABASE=neo4j
11 |
12 | GRAPHQL_SERVER_HOST=0.0.0.0
13 | GRAPHQL_SERVER_PORT=4001
14 | GRAPHQL_SERVER_PATH=/graphql
--------------------------------------------------------------------------------
/mobile_client_flutter/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/scripts/README.md:
--------------------------------------------------------------------------------
1 | # Scripts
2 |
3 | This directory contains scripts for working with the GRANDstack Starter monorepo and should be run as npm scripts from the root directory. For example
4 |
5 | ```
6 | npm run start
7 | ```
8 |
9 | - `start-dev.js` - starts the GraphQL API and web-react servers
10 | - `build.js` - builds the api and web-react projects
11 | - `inferSchema.js` - connect to Neo4j and generate inferred GraphQL type definitions, written to `./api/src/schema.graphql`
12 | - `seed.js` - seeds the database locally from the api directory
13 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/web-react/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 | import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client'
5 |
6 | const client = new ApolloClient({
7 | uri: process.env.REACT_APP_GRAPHQL_URI,
8 | cache: new InMemoryCache(),
9 | })
10 |
11 | it('renders without crashing', () => {
12 | const div = document.createElement('div')
13 | ReactDOM.render(
14 |
15 |
16 | ,
17 | div
18 | )
19 | ReactDOM.unmountComponentAtNode(div)
20 | })
21 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grand-stack-starter",
3 | "env": {
4 | "NEO4J_URI": {
5 | "description": "The bolt endpoint for your neo4j database. For example: bolt://grandstack.io:7687",
6 | "required": true
7 | },
8 | "NEO4J_USER": {
9 | "description": "The database user that the API application will use to connect to Neo4j",
10 | "required": true
11 | },
12 | "NEO4J_PASSWORD": {
13 | "description": "The password for the provided Neo4j database user",
14 | "required": true
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/neo4j/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM neo4j:3.5.12
2 |
3 | ENV NEO4J_AUTH=neo4j/letmein \
4 | APOC_VERSION=3.5.0.5 \
5 | GRAPHQL_VERSION=3.5.0.4
6 |
7 | ENV APOC_URI https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/download/${APOC_VERSION}/apoc-${APOC_VERSION}-all.jar
8 | RUN sh -c 'cd /var/lib/neo4j/plugins && curl -L -O "${APOC_URI}"'
9 |
10 | ENV GRAPHQL_URI https://github.com/neo4j-graphql/neo4j-graphql/releases/download/${GRAPHQL_VERSION}/neo4j-graphql-${GRAPHQL_VERSION}.jar
11 | RUN sh -c 'cd /var/lib/neo4j/plugins && curl -L -O "${GRAPHQL_URI}"'
12 |
13 | EXPOSE 7474 7473 7687
14 |
15 | CMD ["neo4j"]
16 |
--------------------------------------------------------------------------------
/web-react/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import './index.css'
4 | import App from './App'
5 | import registerServiceWorker from './registerServiceWorker'
6 | import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client'
7 |
8 | const client = new ApolloClient({
9 | uri: process.env.REACT_APP_GRAPHQL_URI || '/graphql',
10 | cache: new InMemoryCache(),
11 | })
12 |
13 | const Main = () => (
14 |
15 |
16 |
17 | )
18 |
19 | ReactDOM.render(, document.getElementById('root'))
20 | registerServiceWorker()
21 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:react/recommended",
10 | "plugin:prettier/recommended"
11 | ],
12 | "globals": {
13 | "Atomics": "readonly",
14 | "SharedArrayBuffer": "readonly"
15 | },
16 | "parserOptions": {
17 | "ecmaFeatures": {
18 | "jsx": true
19 | },
20 | "ecmaVersion": 11,
21 | "sourceType": "module"
22 | },
23 | "plugins": ["react", "prettier"],
24 | "rules": {
25 | "prettier/prettier": "error",
26 | "react/prop-types": "off"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/web-angular/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { BusinessesComponent } from './businesses/businesses.component';
4 | import { UsersComponent } from './users/users.component';
5 |
6 |
7 | const routes: Routes = [
8 | { path: '', redirectTo: 'users', pathMatch: 'full' },
9 | { path: 'users', component: UsersComponent },
10 | { path: 'businesses', component: BusinessesComponent }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forRoot(routes)],
15 | exports: [RouterModule]
16 | })
17 | export class AppRoutingModule { }
18 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | command = "npm i && cd api && npm i && cd ../web-react && npm i && cd .. && npm run build && cp api/build/graphql-schema.js api/build/functions/graphql/. && cp api/build/schema.graphql api/build/functions/graphql/. && cd api/src/functions/graphql && npm i"
3 | publish = "web-react/build"
4 | functions = "api/build/functions"
5 |
6 | [dev]
7 | command = "npm start"
8 |
9 | [template.environment]
10 | NEO4J_URI = "Neo4j URI (ex: bolt://localhost:7687)"
11 | NEO4J_USER = "Neo4j User"
12 | NEO4J_PASSWORD = "Neo4j Password"
13 |
14 | [[redirects]]
15 | from = "/graphql"
16 | to = "/.netlify/functions/graphql"
17 | status = 200
--------------------------------------------------------------------------------
/web-react-ts/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 | import { ApolloClient, ApolloProvider, InMemoryCache } from '@apollo/client'
5 |
6 | const uri = process.env.REACT_APP_GRAPHQL_URI || '/graphql'
7 | const cache = new InMemoryCache();
8 |
9 | const client = new ApolloClient({
10 | uri,
11 | cache
12 | })
13 |
14 | it('renders without crashing', () => {
15 | const div = document.createElement('div')
16 | ReactDOM.render(
17 |
18 |
19 | ,
20 | div
21 | )
22 | ReactDOM.unmountComponentAtNode(div)
23 | })
24 |
--------------------------------------------------------------------------------
/web-react-ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["es6", "dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "noEmit": true,
16 | "jsx": "react",
17 | "noImplicitAny": true,
18 | "noImplicitThis": true,
19 | "strictNullChecks": true
20 | },
21 | "include": ["src"]
22 | }
23 |
--------------------------------------------------------------------------------
/web-angular/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "outDir": "./dist/out-tsc",
6 | "sourceMap": true,
7 | "declaration": false,
8 | "downlevelIteration": true,
9 | "experimentalDecorators": true,
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2018",
19 | "dom"
20 | ]
21 | },
22 | "angularCompilerOptions": {
23 | "fullTemplateTypeCheck": true,
24 | "strictInjectionParameters": true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/mobile_client_flutter/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client_flutter",
3 | "short_name": "client_flutter",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "GRANDstack Flutter Client",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.50'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.5.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/widgets/rating_display.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:smooth_star_rating/smooth_star_rating.dart';
3 |
4 | class RatingDisplay extends StatelessWidget {
5 | final double rating;
6 | final double size;
7 |
8 | const RatingDisplay({
9 | Key key,
10 | @required this.rating,
11 | this.size = 20.0,
12 | }) : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return SmoothStarRating(
17 | rating: rating,
18 | allowHalfRating: true,
19 | isReadOnly: true,
20 | starCount: 5,
21 | size: size,
22 | color: Theme.of(context).accentColor,
23 | borderColor: Theme.of(context).accentColor,
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/scripts/common.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const execa = require('execa')
3 |
4 | const { templateName, templateFileName } = require('./config')
5 | const API_DIR = path.join(__dirname, '../api')
6 | const TEMPLATE_DIR = path.join(__dirname, `../${templateFileName}`)
7 |
8 | const shouldUseYarn = () => {
9 | try {
10 | execa.sync('yarnpkg', ['--version'])
11 | return true
12 | } catch (e) {
13 | return false
14 | }
15 | }
16 |
17 | const runner = shouldUseYarn() ? 'yarn' : 'npm'
18 |
19 | const concurrentOpts = {
20 | restartTries: 3,
21 | prefix: '{time} {name} |',
22 | timestampFormat: 'HH:mm:ss',
23 | }
24 |
25 | module.exports = {
26 | templateName,
27 | templateFileName,
28 | concurrentOpts,
29 | runner,
30 | API_DIR,
31 | TEMPLATE_DIR,
32 | }
33 |
--------------------------------------------------------------------------------
/web-angular/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | graphqlServer: '/graphql'
8 | };
9 |
10 | /*
11 | * For easier debugging in development mode, you can import the following file
12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
13 | *
14 | * This import should be commented out in production mode because it will have a negative impact
15 | * on performance if an error is thrown.
16 | */
17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
18 |
--------------------------------------------------------------------------------
/scripts/start-dev.js:
--------------------------------------------------------------------------------
1 | const concurrently = require('concurrently')
2 |
3 | const {
4 | API_DIR,
5 | TEMPLATE_DIR,
6 | runner,
7 | concurrentOpts,
8 | templateName,
9 | } = require('./common')
10 |
11 | const jobs = [
12 | {
13 | name: 'api',
14 | command: `cd ${API_DIR} && ${runner} run start:dev`,
15 | prefixColor: 'green',
16 | },
17 | ]
18 |
19 | if (templateName === 'Flutter') {
20 | jobs.push({
21 | name: templateName,
22 | command: `cd ${TEMPLATE_DIR} && flutter run`,
23 | prefixColor: 'blue',
24 | })
25 | } else {
26 | jobs.push({
27 | name: templateName,
28 | command: `cd ${TEMPLATE_DIR} && ${runner} run start`,
29 | prefixColor: 'blue',
30 | })
31 | }
32 |
33 | concurrently(jobs, concurrentOpts).catch((e) => {
34 | console.error(e.message)
35 | })
36 |
--------------------------------------------------------------------------------
/scripts/build.js:
--------------------------------------------------------------------------------
1 | const concurrently = require('concurrently')
2 |
3 | const {
4 | API_DIR,
5 | TEMPLATE_DIR,
6 | runner,
7 | concurrentOpts,
8 | templateName,
9 | } = require('./common')
10 |
11 | const jobs = [
12 | {
13 | name: 'api',
14 | command: `cd ${API_DIR} && ${runner} run build`,
15 | prefixColor: 'green',
16 | },
17 | ]
18 |
19 | if (templateName === 'Flutter') {
20 | jobs.push({
21 | name: templateName,
22 | command: `cd ${TEMPLATE_DIR} && flutter build appbundle`,
23 | prefixColor: 'blue',
24 | })
25 | } else {
26 | jobs.push({
27 | name: templateName,
28 | command: `cd ${TEMPLATE_DIR} && ${runner} run build`,
29 | prefixColor: 'blue',
30 | })
31 | }
32 |
33 | concurrently(jobs, concurrentOpts).catch((e) => {
34 | console.error(e.message)
35 | })
36 |
--------------------------------------------------------------------------------
/web-angular/src/app/types.ts:
--------------------------------------------------------------------------------
1 | export type User = {
2 | id: string;
3 | name: string;
4 | avgStars: number;
5 | numReviews: number;
6 | recommendations?: Business[];
7 | }
8 |
9 | export type Business = {
10 | id: string;
11 | name: string;
12 | address: string;
13 | city: string;
14 | state: string;
15 | avgStars: number;
16 | reviews?: Review[];
17 | }
18 |
19 | export type Review = {
20 | id: string;
21 | stars: number;
22 | text: string;
23 | date: Neo4jDate;
24 | business?: Business;
25 | user?: User;
26 | }
27 |
28 | export type Neo4jDate = {
29 | year: number;
30 | month: number;
31 | day: number;
32 | formatted: string;
33 | }
34 |
35 | export type Query = {
36 | User?: User[];
37 | Business?: Business[];
38 | Review?: Review[];
39 | usersBySubstring?: User[];
40 | }
41 |
--------------------------------------------------------------------------------
/api/babel.config.js:
--------------------------------------------------------------------------------
1 | const TARGETS_NODE = '12.13.0'
2 | const CORE_JS_VERSION = '3.6'
3 |
4 | module.exports = {
5 | presets: [
6 | [
7 | '@babel/preset-env',
8 | {
9 | targets: { node: TARGETS_NODE },
10 | useBuiltIns: 'usage',
11 | corejs: {
12 | version: CORE_JS_VERSION,
13 | proposals: true,
14 | },
15 | },
16 | ],
17 | ],
18 | plugins: [
19 | [
20 | 'babel-plugin-module-resolver',
21 | {
22 | alias: {
23 | src: './src',
24 | },
25 | },
26 | ],
27 | ['@babel/plugin-proposal-class-properties', { loose: true }],
28 | [
29 | '@babel/plugin-transform-runtime',
30 | {
31 | corejs: { version: 3, proposals: true },
32 | version: '^7.8.3',
33 | },
34 | ],
35 | ],
36 | }
37 |
--------------------------------------------------------------------------------
/web-angular/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web-angular/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.sass-cache
36 | /connect.lock
37 | /coverage
38 | /libpeerconnection.log
39 | npm-debug.log
40 | yarn-error.log
41 | testem.log
42 | /typings
43 |
44 | # System Files
45 | .DS_Store
46 | Thumbs.db
47 |
--------------------------------------------------------------------------------
/scripts/inferSchema.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config({ path: './api/.env' })
2 | const execa = require('execa')
3 | const path = require('path')
4 | const grandstackCmd = path.join('node_modules', '.bin', 'grandstack')
5 | const {
6 | NEO4J_URI,
7 | NEO4J_USER,
8 | NEO4J_PASSWORD,
9 | NEO4J_ENCRYPTED,
10 | NEO4J_DATABASE,
11 | } = process.env
12 |
13 | const grandstackCmdArgs = [
14 | 'graphql',
15 | 'inferschema',
16 | '--neo4j-uri',
17 | `${NEO4J_URI}`,
18 | '--neo4j-user',
19 | `${NEO4J_USER}`,
20 | `--neo4j-password`,
21 | `${NEO4J_PASSWORD}`,
22 | `--schema-file`,
23 | `./api/src/schema.graphql`,
24 | ]
25 |
26 | if (NEO4J_ENCRYPTED) {
27 | grandstackCmdArgs.push(`--encrypted`)
28 | }
29 |
30 | if (NEO4J_DATABASE) {
31 | grandstackCmdArgs.push(`--database`)
32 | grandstackCmdArgs.push(`${NEO4J_DATABASE}`)
33 | }
34 |
35 | execa.sync(grandstackCmd, grandstackCmdArgs)
36 |
--------------------------------------------------------------------------------
/mobile_client_flutter/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: client_flutter
2 | description: Flutter client for GrandStack
3 |
4 | publish_to: "none" # Remove this line if you wish to publish to pub.dev
5 |
6 | version: 1.0.0+1
7 |
8 | environment:
9 | sdk: ">=2.7.0 <3.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | cupertino_icons: ^0.1.3
16 |
17 | graphql_flutter: ^3.1.0
18 | json_annotation: ^3.0.1
19 | smooth_star_rating: ^1.1.1
20 |
21 | dev_dependencies:
22 | flutter_test:
23 | sdk: flutter
24 |
25 | build_runner: ^1.10.1
26 | flutter_launcher_icons: ^0.7.3
27 | json_serializable: ^3.4.0
28 |
29 | # The following section is specific to Flutter.
30 | flutter:
31 | uses-material-design: true
32 |
33 | assets:
34 | - assets/images/
35 |
36 | flutter_icons:
37 | android: "launcher_icon"
38 | ios: true
39 | image_path: "assets/icons/grandstack_icon.png"
40 |
--------------------------------------------------------------------------------
/api/src/functions/graphql/graphql.js:
--------------------------------------------------------------------------------
1 | // This module can be used to serve the GraphQL endpoint
2 | // as a lambda function
3 |
4 | const { ApolloServer } = require('apollo-server-lambda')
5 | import { Neo4jGraphQL } from '@neo4j/graphql'
6 | const neo4j = require('neo4j-driver')
7 |
8 | // This module is copied during the build step
9 | // Be sure to run `npm run build`
10 | const { typeDefs } = require('./graphql-schema')
11 |
12 | const driver = neo4j.driver(
13 | process.env.NEO4J_URI || 'bolt://localhost:7687',
14 | neo4j.auth.basic(
15 | process.env.NEO4J_USER || 'neo4j',
16 | process.env.NEO4J_PASSWORD || 'neo4j'
17 | )
18 | )
19 |
20 | const neoSchema = new Neo4jGraphQL({ typeDefs, driver })
21 |
22 | const server = new ApolloServer({
23 | schema: neoSchema.schema,
24 | context: { driver, neo4jDatabase: process.env.NEO4J_DATABASE },
25 | })
26 |
27 | exports.handler = server.createHandler()
28 |
--------------------------------------------------------------------------------
/web-react-ts/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 | import { ApolloClient, InMemoryCache } from '@apollo/client';
7 | import { ApolloProvider } from '@apollo/client';
8 |
9 | const uri = process.env.REACT_APP_GRAPHQL_URI || 'http://localhost:4001/graphql'
10 | const cache = new InMemoryCache();
11 |
12 | const client = new ApolloClient({
13 | uri,
14 | cache
15 | })
16 |
17 | ReactDOM.render(
18 |
19 |
20 | ,
21 | document.getElementById('root')
22 | );
23 |
24 | // If you want your app to work offline and load faster, you can change
25 | // unregister() to register() below. Note this comes with some pitfalls.
26 | // Learn more about service workers: https://bit.ly/CRA-PWA
27 | serviceWorker.unregister();
28 |
--------------------------------------------------------------------------------
/mobile_client_flutter/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | **/ios/Flutter/.last_build_id
26 | .dart_tool/
27 | .flutter-plugins
28 | .flutter-plugins-dependencies
29 | .packages
30 | .pub-cache/
31 | .pub/
32 | /build/
33 |
34 | # Web related
35 | lib/generated_plugin_registrant.dart
36 |
37 | # Symbolication related
38 | app.*.symbols
39 |
40 | # Obfuscation related
41 | app.*.map.json
42 |
43 | # Exceptions to above rules.
44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
45 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/web-angular/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { AppRoutingModule } from './app-routing.module';
4 | import { GraphQLModule } from './graphql/graphql.module';
5 | import { AppComponent } from './app.component';
6 | import { UsersComponent } from './users/users.component';
7 | import { ClarityModule } from '@clr/angular';
8 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
9 | import { BusinessesComponent } from './businesses/businesses.component';
10 |
11 | @NgModule({
12 | declarations: [
13 | AppComponent,
14 | UsersComponent,
15 | BusinessesComponent
16 | ],
17 | imports: [
18 | BrowserModule,
19 | AppRoutingModule,
20 | ClarityModule,
21 | BrowserAnimationsModule,
22 | GraphQLModule
23 | ],
24 | providers: [],
25 | bootstrap: [AppComponent]
26 | })
27 | export class AppModule { }
28 |
--------------------------------------------------------------------------------
/web-angular/src/app/graphql/graphql.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { HttpClientModule } from '@angular/common/http';
3 | // Apollo
4 | import { ApolloModule, Apollo } from 'apollo-angular';
5 | import { HttpLinkModule, HttpLink } from 'apollo-angular-link-http';
6 | import { InMemoryCache } from 'apollo-cache-inmemory';
7 | import { environment} from '../../environments/environment';
8 |
9 | const uri = environment.graphqlServer;
10 |
11 | @NgModule({
12 | exports: [
13 | HttpClientModule,
14 | ApolloModule,
15 | HttpLinkModule
16 | ]
17 | })
18 | export class GraphQLModule {
19 | constructor(
20 | apollo: Apollo,
21 | httpLink: HttpLink
22 | ) {
23 | apollo.create({
24 | link: httpLink.create({ uri }),
25 | cache: new InMemoryCache(),
26 | defaultOptions: {
27 | watchQuery: {
28 | errorPolicy: 'all'
29 | }
30 | }
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 |
3 | services:
4 | neo4j:
5 | build: ./neo4j
6 | ports:
7 | - 7474:7474
8 | - 7687:7687
9 | environment:
10 | - NEO4J_dbms_security_procedures_unrestricted=apoc.*
11 | - NEO4J_apoc_import_file_enabled=true
12 | - NEO4J_apoc_export_file_enabled=true
13 | - NEO4J_dbms_shell_enabled=true
14 |
15 | api:
16 | build: ./api
17 | ports:
18 | - 4001:4001
19 | environment:
20 | - NEO4J_URI=bolt://neo4j:7687
21 | - NEO4J_USER=neo4j
22 | - NEO4J_PASSWORD=letmein
23 | - GRAPHQL_LISTEN_PORT=4001
24 | - GRAPHQL_URI=http://api:4001/graphql
25 |
26 | links:
27 | - neo4j
28 | depends_on:
29 | - neo4j
30 |
31 | ui:
32 | build: ./web-react
33 | ports:
34 | - 3000:3000
35 | environment:
36 | - CI=true
37 | - REACT_APP_GRAPHQL_URI=/graphql
38 | - PROXY=http://api:4001/graphql
39 | links:
40 | - api
41 | depends_on:
42 | - api
43 |
--------------------------------------------------------------------------------
/web-react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grand-stack-starter-web-react",
3 | "version": "0.0.1",
4 | "description": "React app for GRANDstack Starter",
5 | "dependencies": {
6 | "@apollo/client": "^3.1.3",
7 | "@material-ui/core": "^4.10.0",
8 | "@material-ui/icons": "^4.9.1",
9 | "classnames": "^2.2.6",
10 | "graphql": "^14.5.8",
11 | "moment": "^2.29.1",
12 | "react": "^17.0.1",
13 | "react-dom": "^17.0.1",
14 | "react-router": "^5.1.2",
15 | "react-router-dom": "^5.1.2",
16 | "react-scripts": "^4.0.2",
17 | "recharts": "^1.8.5"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test --env=jsdom",
23 | "eject": "react-scripts eject",
24 | "now-build": "react-scripts build"
25 | },
26 | "devDependencies": {
27 | "http-proxy-middleware": "^0.20.0"
28 | },
29 | "browserslist": [
30 | ">0.2%",
31 | "not dead",
32 | "not ie <= 11",
33 | "not op_mini all"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/widgets/business_list_tile.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:client_flutter/model/model.dart';
3 | import 'package:client_flutter/screens/business_detail_screen.dart';
4 | import 'rating_display.dart';
5 |
6 | class BusinessListTile extends StatelessWidget {
7 | const BusinessListTile({
8 | Key key,
9 | @required this.business,
10 | }) : super(key: key);
11 |
12 | final Business business;
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return ListTile(
17 | title: Text(business.name),
18 | subtitle: Text(business.city + ', ' + business.state),
19 | trailing: RatingDisplay(
20 | rating: business.avgStars,
21 | size: 20.0,
22 | ),
23 | onTap: () {
24 | Navigator.push(
25 | context,
26 | MaterialPageRoute(
27 | builder: (context) =>
28 | BusinessDetailScreen(businessId: business.businessId),
29 | ),
30 | );
31 | },
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/api/src/seed/seed-db.js:
--------------------------------------------------------------------------------
1 | import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
2 | import dotenv from 'dotenv'
3 | import fetch from 'node-fetch'
4 | import { getSeedMutations } from './seed-mutations'
5 |
6 | dotenv.config()
7 |
8 | const {
9 | GRAPHQL_SERVER_HOST: host,
10 | GRAPHQL_SERVER_PORT: port,
11 | GRAPHQL_SERVER_PATH: path,
12 | } = process.env
13 |
14 | const uri = `http://${host}:${port}${path}`
15 |
16 | const client = new ApolloClient({
17 | link: new HttpLink({ uri, fetch }),
18 | cache: new InMemoryCache(),
19 | })
20 |
21 | const runMutations = async () => {
22 | const mutations = await getSeedMutations()
23 |
24 | return Promise.all(
25 | mutations.map(({ mutation, variables }) => {
26 | return client
27 | .mutate({
28 | mutation,
29 | variables,
30 | })
31 | .catch((e) => {
32 | throw new Error(e)
33 | })
34 | })
35 | )
36 | }
37 |
38 | runMutations()
39 | .then(() => {
40 | console.log('Database seeded!')
41 | })
42 | .catch((e) => console.error(e))
43 |
--------------------------------------------------------------------------------
/mobile_client_flutter/README.md:
--------------------------------------------------------------------------------
1 | # GRANDstack Flutter Client
2 |
3 | A rich native mobile UI for GRANDstack which runs on Android, iOS, and web.
4 |
5 | 
6 |
7 | ## Getting Started
8 |
9 | 1. Install Flutter on your machine using these [instructions](https://flutter.dev/docs/get-started/install).
10 |
11 | 2. Optionally enable [web support](https://flutter.dev/docs/get-started/web) by switching to the beta channel.
12 |
13 | 3. Run `flutter pub get` to install dependencies.
14 |
15 | 4. `flutter run` to launch your app in debug mode. Or use the Flutter tools in your IDE.
16 |
17 | ## Build
18 |
19 | If you change `lib/model/model.dart` you will need to regenerate the JSON serialization code by running this command:
20 |
21 | ```
22 | flutter pub run build_runner build
23 | ```
24 |
25 | If you wish to change the app icon in `assets/icons` you will need to update the source file in `pubspec.yaml`. This updates Android and iOS icons, web must be done manually. Run the following command:
26 |
27 | ```
28 | flutter pub run flutter_launcher_icons:main
29 | ```
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "concurrently": "^5.2.0",
4 | "eslint": "^7.19.0",
5 | "eslint-config-prettier": "^6.11.0",
6 | "eslint-plugin-prettier": "^3.1.3",
7 | "eslint-plugin-react": "^7.20.0",
8 | "execa": "^4.0.2",
9 | "grandstack": "^0.1.7",
10 | "dotenv": "^8.2.0",
11 | "husky": ">=4",
12 | "lint-staged": ">=10",
13 | "prettier": "^2.0.5",
14 | "prettier-eslint-cli": "^5.0.0"
15 | },
16 | "scripts": {
17 | "seedDb": "node scripts/seed.js",
18 | "start": "node scripts/start-dev.js",
19 | "build": "node scripts/build.js",
20 | "format": "find . -name \"*.js\" | grep -v node_modules | grep -v build | xargs prettier --write",
21 | "format:log": "find . -name \"*.js\" | grep -v node_modules | grep -v build | xargs prettier",
22 | "inferschema:write": "node scripts/inferSchema.js"
23 | },
24 | "husky": {
25 | "hooks": {
26 | "pre-commit": "lint-staged"
27 | }
28 | },
29 | "lint-staged": {
30 | "*.js": [
31 | "prettier --write",
32 | "eslint --fix"
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:graphql_flutter/graphql_flutter.dart';
3 | import 'package:client_flutter/services/gql.dart';
4 | import 'package:client_flutter/screens/business_list_screen.dart';
5 | import 'package:client_flutter/screens/user_list_screen.dart';
6 |
7 | void main() {
8 | runApp(MyApp());
9 | }
10 |
11 | class MyApp extends StatelessWidget {
12 | // This widget is the root of your application.
13 | @override
14 | Widget build(BuildContext context) {
15 | return GraphQLProvider(
16 | client: client,
17 | child: MaterialApp(
18 | title: 'GrandStack Flutter',
19 | theme: ThemeData(
20 | primarySwatch: Colors.indigo,
21 | accentColor: Colors.pinkAccent,
22 | visualDensity: VisualDensity.adaptivePlatformDensity,
23 | ),
24 | initialRoute: '/',
25 | routes: {
26 | '/': (context) => BusinessListScreen(),
27 | '/users': (context) => UserListScreen(),
28 | },
29 | debugShowCheckedModeBanner: false,
30 | ),
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/web-angular/README.md:
--------------------------------------------------------------------------------
1 | # NgClient
2 |
3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.19.
4 |
5 | ## Development server
6 |
7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8 |
9 | ## Code scaffolding
10 |
11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12 |
13 | ## Build
14 |
15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
16 |
17 | ## Running unit tests
18 |
19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20 |
21 | ## Running end-to-end tests
22 |
23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24 |
25 | ## Further help
26 |
27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
28 |
--------------------------------------------------------------------------------
/web-react/src/components/UserCount.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { makeStyles } from '@material-ui/core/styles'
4 | import Typography from '@material-ui/core/Typography'
5 | import Title from './Title'
6 | import { useQuery, gql } from '@apollo/client'
7 |
8 | const useStyles = makeStyles({
9 | depositContext: {
10 | flex: 1,
11 | },
12 | navLink: {
13 | textDecoration: 'none',
14 | },
15 | })
16 |
17 | const GET_COUNT_QUERY = gql`
18 | {
19 | userCount
20 | }
21 | `
22 |
23 | export default function Deposits() {
24 | const classes = useStyles()
25 |
26 | const { loading, error, data } = useQuery(GET_COUNT_QUERY)
27 | if (error) return Error
28 | return (
29 |
30 | Total Users
31 |
32 | {loading ? 'Loading...' : data.userCount}
33 |
34 |
35 | users found
36 |
37 |
38 |
39 | View users
40 |
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/web-react-ts/src/components/UserCount.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { makeStyles } from '@material-ui/core/styles'
4 | import Typography from '@material-ui/core/Typography'
5 | import Title from './Title'
6 | import { useQuery, gql } from '@apollo/client'
7 |
8 | const useStyles = makeStyles({
9 | depositContext: {
10 | flex: 1,
11 | },
12 | navLink: {
13 | textDecoration: 'none',
14 | },
15 | })
16 |
17 | const GET_COUNT_QUERY = gql`
18 | {
19 | userCount
20 | }
21 | `
22 |
23 | export default function Deposits() {
24 | const classes = useStyles()
25 |
26 | const { loading, error, data } = useQuery(GET_COUNT_QUERY)
27 | if (error) return Error
28 | return (
29 |
30 | Total Users
31 |
32 | {loading ? 'Loading...' : data.userCount}
33 |
34 |
35 | users found
36 |
37 |
38 |
39 | View users
40 |
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/mobile_client_flutter/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | GRANDstack Flutter
18 |
19 |
20 |
21 |
24 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/web-angular/src/app/users/users.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{error}}
9 |
10 |
11 |
12 |
13 |
14 |
15 | Name
16 |
17 | Average Stars
18 |
19 | Number of Reviews
20 |
21 |
22 | {{user.name}}
23 | {{user.avgStars | number: '1.1-1'}}
24 | {{user.numReviews}}
25 |
26 |
27 |
28 |
29 | Items per page
30 | {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/web-react-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "web-react-ts",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@apollo/client": "^3.1.2",
7 | "@material-ui/core": "^4.11.0",
8 | "@material-ui/icons": "^4.9.1",
9 | "moment": "^2.29.1",
10 | "react": "^16.13.1",
11 | "react-dom": "^16.13.1",
12 | "react-router-dom": "^5.2.0",
13 | "react-scripts": "3.4.1",
14 | "recharts": "^1.8.5"
15 | },
16 | "devDependencies": {
17 | "@testing-library/jest-dom": "^4.2.4",
18 | "@testing-library/react": "^9.3.2",
19 | "@testing-library/user-event": "^7.1.2",
20 | "@types/jest": "^24.0.0",
21 | "@types/node": "^12.0.0",
22 | "@types/react": "^16.9.0",
23 | "@types/react-dom": "^16.9.0",
24 | "@types/react-router-dom": "^5.1.5",
25 | "@types/recharts": "^1.8.14",
26 | "typescript": "~3.7.2"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject"
33 | },
34 | "eslintConfig": {
35 | "extends": "react-app"
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/web-angular/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grandstack-starter-web-angular",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve --proxy-config proxy.conf.json",
7 | "build": "ng build",
8 | "test": "ng test",
9 | "lint": "ng lint",
10 | "e2e": "ng e2e"
11 | },
12 | "private": true,
13 | "dependencies": {
14 | "@angular/animations": "~8.2.14",
15 | "@angular/common": "~8.2.14",
16 | "@angular/compiler": "~8.2.14",
17 | "@angular/core": "~8.2.14",
18 | "@angular/forms": "~8.2.14",
19 | "@angular/platform-browser": "~8.2.14",
20 | "@angular/platform-browser-dynamic": "~8.2.14",
21 | "@angular/router": "~8.2.14",
22 | "@clr/angular": "^2.3.3",
23 | "@clr/icons": "2.3.3",
24 | "@clr/ui": "2.3.3",
25 | "@webcomponents/custom-elements": "^1.0.0",
26 | "apollo-angular": "^1.8.0",
27 | "apollo-angular-link-http": "^1.9.0",
28 | "apollo-cache-inmemory": "^1.6.3",
29 | "apollo-client": "^2.6.4",
30 | "apollo-link-error": "^1.1.12",
31 | "graphql": "^14.5.8",
32 | "graphql-tag": "^2.10.1",
33 | "rxjs": "~6.4.0",
34 | "tslib": "^1.10.0",
35 | "zone.js": "~0.9.1"
36 | },
37 | "devDependencies": {
38 | "@angular-devkit/build-angular": "^0.803.25",
39 | "@angular/cli": "~8.3.19",
40 | "@angular/compiler-cli": "~8.2.14",
41 | "@angular/language-service": "~8.2.14",
42 | "@types/node": "~8.9.4",
43 | "ts-node": "~7.0.0",
44 | "tslint": "~5.15.0",
45 | "typescript": "~3.5.3"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/web-react/src/components/RatingsChart.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTheme } from '@material-ui/core/styles'
3 | import {
4 | Bar,
5 | XAxis,
6 | YAxis,
7 | Label,
8 | ResponsiveContainer,
9 | BarChart,
10 | } from 'recharts'
11 | import { useQuery, gql } from '@apollo/client'
12 | import Title from './Title'
13 |
14 | const GET_DATA_QUERY = gql`
15 | {
16 | ratingsCount {
17 | stars
18 | count
19 | }
20 | }
21 | `
22 |
23 | export default function RatingsChart() {
24 | const theme = useTheme()
25 |
26 | const { loading, error, data } = useQuery(GET_DATA_QUERY)
27 | if (error) return Error
28 | if (loading) return Loading
29 |
30 | return (
31 |
32 | Ratings Distribution
33 |
34 |
43 |
44 |
45 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/web-react-ts/src/components/RatingsChart.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTheme } from '@material-ui/core/styles'
3 | import {
4 | Bar,
5 | XAxis,
6 | YAxis,
7 | Label,
8 | ResponsiveContainer,
9 | BarChart,
10 | } from 'recharts'
11 | import { useQuery, gql } from '@apollo/client'
12 | import Title from './Title'
13 |
14 | const GET_DATA_QUERY = gql`
15 | {
16 | ratingsCount {
17 | stars
18 | count
19 | }
20 | }
21 | `
22 |
23 | export default function RatingsChart() {
24 | const theme = useTheme()
25 |
26 | const { loading, error, data } = useQuery(GET_DATA_QUERY)
27 | if (error) return Error
28 | if (loading) return Loading
29 |
30 | return (
31 |
32 | Ratings Distribution
33 |
34 |
43 |
44 |
45 |
52 |
53 |
54 |
55 |
56 |
57 | )
58 | }
59 |
--------------------------------------------------------------------------------
/web-react/src/components/Dashboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTheme } from '@material-ui/core/styles'
3 | import { Grid, Paper } from '@material-ui/core'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import clsx from 'clsx'
6 |
7 | import RatingsChart from './RatingsChart'
8 | import UserCount from './UserCount'
9 | import RecentReviews from './RecentReviews'
10 | export default function Dashboard() {
11 | const theme = useTheme()
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | root: {
15 | display: 'flex',
16 | },
17 | paper: {
18 | padding: theme.spacing(2),
19 | display: 'flex',
20 | overflow: 'auto',
21 | flexDirection: 'column',
22 | },
23 | fixedHeight: {
24 | height: 240,
25 | },
26 | }))
27 | const classes = useStyles(theme)
28 | const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight)
29 |
30 | return (
31 |
32 |
33 | {/* Ratings Chart */}
34 |
35 |
36 |
37 |
38 |
39 | {/* User Count */}
40 |
41 |
42 |
43 |
44 |
45 | {/* Recent Reviews */}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/web-react-ts/src/components/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTheme } from '@material-ui/core/styles'
3 | import { Grid, Paper } from '@material-ui/core'
4 | import { makeStyles } from '@material-ui/core/styles'
5 | import clsx from 'clsx'
6 |
7 | import RatingsChart from './RatingsChart'
8 | import UserCount from './UserCount'
9 | import RecentReviews from './RecentReviews'
10 | export default function Dashboard() {
11 | const theme = useTheme()
12 |
13 | const useStyles = makeStyles((theme) => ({
14 | root: {
15 | display: 'flex',
16 | },
17 | paper: {
18 | padding: theme.spacing(2),
19 | display: 'flex',
20 | overflow: 'auto',
21 | flexDirection: 'column',
22 | },
23 | fixedHeight: {
24 | height: 240,
25 | },
26 | }))
27 | const classes = useStyles(theme)
28 | const fixedHeightPaper = clsx(classes.paper, classes.fixedHeight)
29 |
30 | return (
31 |
32 |
33 | {/* Ratings Chart */}
34 |
35 |
36 |
37 |
38 |
39 | {/* User Count */}
40 |
41 |
42 |
43 |
44 |
45 | {/* Recent Reviews */}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/web-angular/src/app/businesses/businesses.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{error}}
9 |
10 |
11 |
12 |
13 |
14 |
15 | Name
16 | Address
17 | City
18 | State
19 |
20 | Average Stars
21 |
22 |
23 |
24 | {{business.name}}
25 | {{business.address}}
26 | {{business.city}}
27 | {{business.state}}
28 | {{business.avgStars | number: '1.1-1'}}
29 |
30 |
31 |
32 |
33 | Items per page
34 | {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "grand-stack-starter-api",
3 | "version": "0.0.1",
4 | "description": "API app for GRANDstack",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start:dev": "./node_modules/.bin/nodemon --watch src --ext js,graphql --exec babel-node src/index.js",
9 | "build": "babel src --out-dir build && shx cp .env build 2>/dev/null || : && shx cp src/schema.graphql build",
10 | "now-build": "babel src --out-dir build && shx cp src/schema.graphql build",
11 | "start": "npm run build && node build/index.js",
12 | "seedDb": "./node_modules/.bin/babel-node src/seed/seed-db.js"
13 | },
14 | "author": "William Lyon",
15 | "license": "MIT",
16 | "dependencies": {
17 | "@apollo/client": "^3.2.5",
18 | "@neo4j/graphql": "^1.0.0",
19 | "apollo-server": "^2.19.2",
20 | "apollo-server-lambda": "^2.19.0",
21 | "csv-parse": "^4.10.1",
22 | "dotenv": "^7.0.0",
23 | "neo4j-driver": "^4.2.2",
24 | "node-fetch": "^2.6.0",
25 | "react": "^16.13.1"
26 | },
27 | "devDependencies": {
28 | "@babel/cli": "^7.8.4",
29 | "@babel/core": "^7.9.0",
30 | "@babel/node": "^7.8.7",
31 | "@babel/plugin-proposal-class-properties": "^7.8.3",
32 | "@babel/plugin-transform-runtime": "^7.9.0",
33 | "@babel/preset-env": "^7.9.0",
34 | "@babel/preset-react": "^7.9.4",
35 | "@babel/preset-typescript": "^7.9.0",
36 | "@babel/runtime-corejs3": "^7.9.2",
37 | "babel-plugin-auto-import": "^1.0.5",
38 | "babel-plugin-module-resolver": "^4.0.0",
39 | "cross-env": "^7.0.2",
40 | "nodemon": "^1.19.1",
41 | "shx": "^0.3.2"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | client_flutter
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIMainStoryboardFile
28 | Main
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 | UIInterfaceOrientationLandscapeLeft
33 | UIInterfaceOrientationLandscapeRight
34 |
35 | UISupportedInterfaceOrientations~ipad
36 |
37 | UIInterfaceOrientationPortrait
38 | UIInterfaceOrientationPortraitUpsideDown
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UIViewControllerBasedStatusBarAppearance
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/widgets/menu_drawer.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | class MenuDrawer extends StatelessWidget {
4 | @override
5 | Widget build(BuildContext context) {
6 | return Drawer(
7 | // Add a ListView to the drawer. This ensures the user can scroll
8 | // through the options in the drawer if there isn't enough vertical
9 | // space to fit everything.
10 | child: ListView(
11 | // Important: Remove any padding from the ListView.
12 | padding: EdgeInsets.zero,
13 | children: [
14 | DrawerHeader(
15 | child: Column(
16 | children: [
17 | Container(
18 | height: 80.0,
19 | child: Image(
20 | image: AssetImage('assets/images/grandstack.png'),
21 | ),
22 | ),
23 | Text(
24 | 'GRANDstack Flutter',
25 | style: Theme.of(context).textTheme.headline6.copyWith(
26 | color: Colors.white,
27 | ),
28 | ),
29 | ],
30 | ),
31 | decoration: BoxDecoration(
32 | color: Colors.indigo[800],
33 | ),
34 | ),
35 | ListTile(
36 | leading: Icon(Icons.business),
37 | title: Text('Businesses'),
38 | onTap: () {
39 | Navigator.pushReplacementNamed(context, '/');
40 | },
41 | ),
42 | ListTile(
43 | leading: Icon(Icons.group),
44 | title: Text('Users'),
45 | onTap: () {
46 | Navigator.pushReplacementNamed(context, '/users');
47 | },
48 | ),
49 | ],
50 | ),
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/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`](https://neo4j.com/docs/graphql-manual/current/type-definitions/cypher/), 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.
6 |
7 | #### Sandbox setup
8 |
9 | A good tutorial can be found here: https://www.youtube.com/watch?v=rPC71lUhK_I
10 |
11 | #### Local setup
12 |
13 | 1. [Download Neo4j Desktop](https://neo4j.com/download/)
14 | 2. Install and open Neo4j Desktop.
15 | 3. Create a new DB by clicking "New Graph", and clicking "create local graph".
16 | 4. Set password to "letmein" (as suggested by `api/.env`), and click "Create".
17 | 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`
18 | 6. Click "Manage".
19 | 7. Click "Plugins".
20 | 8. Find "APOC" and click "Install".
21 | 9. Click the "play" button at the top of left the screen, which should start the server. _(screenshot 2)_
22 | 10. Wait until it says "RUNNING".
23 | 11. Proceed forward with the rest of the tutorial.
24 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | { "src": "api/src/index.js", "use": "@vercel/node" },
5 | {
6 | "src": "web-react/package.json",
7 | "use": "@vercel/static-build",
8 | "config": { "distDir": "build" }
9 | }
10 | ],
11 | "routes": [
12 | { "src": "/graphql(.*)", "dest": "api/src/index.js" },
13 | {
14 | "src": "/static/(.*)",
15 | "headers": { "cache-control": "s-maxage=31536000,immutable" },
16 | "dest": "web-react/static/$1"
17 | },
18 | { "src": "/favicon.ico", "dest": "web-react/favicon.ico" },
19 | { "src": "/img/(.*)", "dest": "web-react/img/$1" },
20 | { "src": "/asset-manifest.json", "dest": "web-react/asset-manifest.json" },
21 | { "src": "/manifest.json", "dest": "web-react/manifest.json" },
22 | {
23 | "src": "/precache-manifest.(.*)",
24 | "dest": "web-react/precache-manifest.$1"
25 | },
26 | {
27 | "src": "/service-worker.js",
28 | "headers": { "cache-control": "s-maxage=0" },
29 | "dest": "web-react/service-worker.js"
30 | },
31 | {
32 | "src": "^(.*)$",
33 | "headers": { "cache-control": "s-maxage=0" },
34 | "dest": "/web-react/index.html"
35 | }
36 | ],
37 | "build": {
38 | "env": {
39 | "REACT_APP_GRAPHQL_URI": "/graphql",
40 | "NEO4J_URI": "@grand_stack_starter_neo4j_uri",
41 | "NEO4J_USER": "@grand_stack_starter_neo4j_user",
42 | "NEO4J_PASSWORD": "@grand_stack_starter_neo4j_password"
43 | }
44 | },
45 | "env": {
46 | "NEO4J_URI": "@grand_stack_starter_neo4j_uri",
47 | "NEO4J_USER": "@grand_stack_starter_neo4j_user",
48 | "NEO4J_PASSWORD": "@grand_stack_starter_neo4j_password",
49 | "REACT_APP_GRAPHQL_URI": "/graphql",
50 | "PROXY": "http://localhost:4001/graphql",
51 | "GRAPHQL_LISTEN_PORT": "4001",
52 | "GRAPHQL_URI": "/graphql"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/web-react-ts/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | GRANDstack App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/web-react/src/components/RecentReviews.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Table from '@material-ui/core/Table'
3 | import TableBody from '@material-ui/core/TableBody'
4 | import TableCell from '@material-ui/core/TableCell'
5 | import TableHead from '@material-ui/core/TableHead'
6 | import TableRow from '@material-ui/core/TableRow'
7 | import { useQuery, gql } from '@apollo/client'
8 | import Title from './Title'
9 | import moment from 'moment'
10 |
11 | const GET_RECENT_REVIEWS_QUERY = gql`
12 | {
13 | reviews(options: { limit: 10, sort: { date: DESC } }) {
14 | user {
15 | name
16 | }
17 | business {
18 | name
19 | }
20 | date
21 | text
22 | stars
23 | }
24 | }
25 | `
26 |
27 | export default function RecentReviews() {
28 | const { loading, error, data } = useQuery(GET_RECENT_REVIEWS_QUERY)
29 | if (error) return Error
30 | if (loading) return Loading
31 |
32 | return (
33 |
34 | Recent Reviews
35 |
36 |
37 |
38 | Date
39 | Business Name
40 | User Name
41 | Review Text
42 | Review Stars
43 |
44 |
45 |
46 | {data.reviews.map((row) => (
47 |
48 | {moment(row.date).format('MMMM Do YYYY')}
49 | {row.business.name}
50 | {row.user.name}
51 | {row.text}
52 | {row.stars}
53 |
54 | ))}
55 |
56 |
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/web-react-ts/src/components/RecentReviews.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Table from '@material-ui/core/Table'
3 | import TableBody from '@material-ui/core/TableBody'
4 | import TableCell from '@material-ui/core/TableCell'
5 | import TableHead from '@material-ui/core/TableHead'
6 | import TableRow from '@material-ui/core/TableRow'
7 | import Title from './Title'
8 | import { useQuery, gql } from '@apollo/client'
9 | import moment from 'moment'
10 |
11 | const GET_RECENT_REVIEWS_QUERY = gql`
12 | {
13 | reviews(options: { limit: 10, sort: { date: DESC } }) {
14 | user {
15 | name
16 | }
17 | business {
18 | name
19 | }
20 | date
21 | text
22 | stars
23 | }
24 | }
25 | `
26 |
27 | export default function RecentReviews() {
28 | const { loading, error, data } = useQuery(GET_RECENT_REVIEWS_QUERY)
29 | if (error) return Error
30 | if (loading) return Loading
31 |
32 | return (
33 |
34 | Recent Reviews
35 |
36 |
37 |
38 | Date
39 | Business Name
40 | User Name
41 | Review Text
42 | Review Stars
43 |
44 |
45 |
46 | {data.reviews.map((row: any) => (
47 |
48 | {moment(row.date).format('MMMM Do YYYY')}
49 | {row.business.name}
50 | {row.user.name}
51 | {row.text}
52 | {row.stars}
53 |
54 | ))}
55 |
56 |
57 |
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/web-angular/src/app/users/users.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable, of } from 'rxjs';
3 | import { catchError, map, tap } from 'rxjs/operators';
4 | import { Apollo } from 'apollo-angular';
5 | import gql from 'graphql-tag';
6 | import { ClrDatagridStateInterface } from '@clr/angular';
7 | import { Query, User } from '../types';
8 | import { getQueryParams, getTotal, getErrorMessage } from '../utils/query-helpers';
9 |
10 | const GET_USERS = gql`
11 | query usersPaginateQuery(
12 | $first: Int
13 | $offset: Int
14 | $orderBy: [_UserOrdering]
15 | $filter: _UserFilter
16 | ) {
17 | User(first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) {
18 | id
19 | name
20 | avgStars
21 | numReviews
22 | }
23 | }
24 | `;
25 |
26 | @Component({
27 | selector: 'app-users',
28 | templateUrl: './users.component.html',
29 | styles: []
30 | })
31 | export class UsersComponent implements OnInit {
32 | error = '';
33 | loading = false;
34 | pageSize: number;
35 | users: Observable;
36 | total = 0;
37 |
38 | constructor(private apollo: Apollo) { }
39 |
40 | ngOnInit() {
41 | this.loading = true;
42 | }
43 |
44 | refresh(state: ClrDatagridStateInterface) {
45 | this.error = '';
46 | this.loading = true;
47 | const { first, offset, filter, orderBy } = getQueryParams(state);
48 | this.users = this.apollo.watchQuery({
49 | query: GET_USERS,
50 | variables: {
51 | first, offset, filter, orderBy
52 | }
53 | })
54 | .valueChanges
55 | .pipe(
56 | map(result => result.data.User),
57 | tap(list => {
58 | this.loading = false;
59 | this.total = getTotal(offset, first, list.length);
60 | }),
61 | catchError(err => {
62 | this.loading = false;
63 | this.error = getErrorMessage(err);
64 | return of([]);
65 | })
66 | );
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/web-react/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
20 |
24 |
33 | GRANDstack App
34 |
35 |
36 |
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 28
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "io.grandstack.client_flutter"
42 | minSdkVersion 16
43 | targetSdkVersion 28
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | }
47 |
48 | buildTypes {
49 | release {
50 | // TODO: Add your own signing config for the release build.
51 | // Signing with the debug keys for now, so `flutter run --release` works.
52 | signingConfig signingConfigs.debug
53 | }
54 | }
55 | }
56 |
57 | flutter {
58 | source '../..'
59 | }
60 |
61 | dependencies {
62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
63 | }
64 |
--------------------------------------------------------------------------------
/web-angular/src/app/utils/query-helpers.ts:
--------------------------------------------------------------------------------
1 | import { ClrDatagridStateInterface } from '@clr/angular';
2 | import { ErrorResponse } from 'apollo-link-error';
3 |
4 | export function getQueryParams(state: ClrDatagridStateInterface) {
5 | // Clarity DataGrid docs at https://v2.clarity.design/datagrid/server-driven
6 | const first = state.page.size;
7 | const offset = Math.max(0, state.page.from);
8 | const filter = getFilters(state);
9 | const orderBy = getSortOrder(state);
10 | return { first, offset, filter, orderBy };
11 | }
12 |
13 | export function getFilters(state: ClrDatagridStateInterface) {
14 | if (!state.filters) {
15 | return {};
16 | }
17 | const gqlFilter: any = {};
18 | state.filters.forEach(filter => {
19 | if (filter.property) {
20 | // string filter
21 | gqlFilter[filter.property + '_contains'] = filter.value;
22 | }
23 | });
24 | return gqlFilter;
25 | }
26 |
27 | export function getSortOrder(state: ClrDatagridStateInterface) {
28 | if (!state.sort) {
29 | return;
30 | }
31 | return state.sort.by + '_' + (state.sort.reverse ? 'desc' : 'asc');
32 | }
33 |
34 | export function getTotal(offset: number, pageSize: number, currentItems: number) {
35 | let total = offset + currentItems;
36 | // If the number of items evenly fill up a page, add 1 to enable the next page
37 | if (total > 0 && total % pageSize === 0) {
38 | total++;
39 | }
40 | return total;
41 | }
42 |
43 | export function getErrorMessage(err: ErrorResponse) {
44 | const { graphQLErrors, networkError } = err;
45 | let message = 'Unknown error';
46 | if (graphQLErrors) {
47 | graphQLErrors.forEach(({ message, locations, path }) =>
48 | console.warn(
49 | `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
50 | ),
51 | );
52 | message = 'GraphQL errors occurred. Check the console for details.';
53 | }
54 | if (networkError) {
55 | console.warn(`[Network error]: ${networkError.message}`);
56 | message = networkError.message;
57 | }
58 | return message;
59 | }
60 |
--------------------------------------------------------------------------------
/web-angular/src/app/businesses/businesses.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Observable, of } from 'rxjs';
3 | import { catchError, map, tap } from 'rxjs/operators';
4 | import { Apollo } from 'apollo-angular';
5 | import gql from 'graphql-tag';
6 | import { ClrDatagridStateInterface } from '@clr/angular';
7 | import { Query, Business } from '../types';
8 | import { getQueryParams, getTotal, getErrorMessage } from '../utils/query-helpers';
9 |
10 | const GET_BUSINESSES = gql`
11 | query businessesPaginateQuery(
12 | $first: Int
13 | $offset: Int
14 | $orderBy: [_BusinessOrdering]
15 | $filter: _BusinessFilter
16 | ) {
17 | Business(first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) {
18 | id
19 | name
20 | address
21 | city
22 | state
23 | avgStars
24 | }
25 | }
26 | `;
27 |
28 | @Component({
29 | selector: 'app-businesses',
30 | templateUrl: './businesses.component.html',
31 | styles: []
32 | })
33 | export class BusinessesComponent implements OnInit {
34 |
35 | businesses: Observable;
36 | error = '';
37 | loading = false;
38 | total = 0;
39 |
40 | constructor(private apollo: Apollo) { }
41 |
42 | ngOnInit() {
43 | this.loading = true;
44 | }
45 |
46 | refresh(state: ClrDatagridStateInterface) {
47 | this.error = '';
48 | this.loading = true;
49 | const { first, offset, filter, orderBy } = getQueryParams(state);
50 | this.businesses = this.apollo.watchQuery({
51 | query: GET_BUSINESSES,
52 | variables: {
53 | first, offset, filter, orderBy
54 | }
55 | })
56 | .valueChanges
57 | .pipe(
58 | map(result => result.data.Business),
59 | tap(list => {
60 | this.loading = false;
61 | this.total = getTotal(offset, first, list.length);
62 | }),
63 | catchError(err => {
64 | this.loading = false;
65 | this.error = getErrorMessage(err);
66 | return of([]);
67 | })
68 | );
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/api/src/index.js:
--------------------------------------------------------------------------------
1 | import { typeDefs } from './graphql-schema'
2 | import { ApolloServer } from 'apollo-server-express'
3 | import express from 'express'
4 | import neo4j from 'neo4j-driver'
5 | import { Neo4jGraphQL } from '@neo4j/graphql'
6 | import dotenv from 'dotenv'
7 |
8 | // set environment variables from .env
9 | dotenv.config()
10 |
11 | const app = express()
12 |
13 | /*
14 | * Create a Neo4j driver instance to connect to the database
15 | * using credentials specified as environment variables
16 | * with fallback to defaults
17 | */
18 | const driver = neo4j.driver(
19 | process.env.NEO4J_URI || 'bolt://localhost:7687',
20 | neo4j.auth.basic(
21 | process.env.NEO4J_USER || 'neo4j',
22 | process.env.NEO4J_PASSWORD || 'neo4j'
23 | )
24 | )
25 |
26 | /*
27 | * Create an executable GraphQL schema object from GraphQL type definitions
28 | * including autogenerated queries and mutations.
29 | * Read more in the docs:
30 | * https://neo4j.com/docs/graphql-manual/current/
31 | */
32 |
33 | const neoSchema = new Neo4jGraphQL({ typeDefs, driver })
34 |
35 | /*
36 | * Create a new ApolloServer instance, serving the GraphQL schema
37 | * created using makeAugmentedSchema above and injecting the Neo4j driver
38 | * instance into the context object so it is available in the
39 | * generated resolvers to connect to the database.
40 | */
41 | const server = new ApolloServer({
42 | context: {
43 | driver,
44 | driverConfig: { database: process.env.NEO4J_DATABASE || 'neo4j' },
45 | },
46 | schema: neoSchema.schema,
47 | introspection: true,
48 | playground: true,
49 | })
50 |
51 | // Specify host, port and path for GraphQL endpoint
52 | const port = process.env.GRAPHQL_SERVER_PORT || 4001
53 | const path = process.env.GRAPHQL_SERVER_PATH || '/graphql'
54 | const host = process.env.GRAPHQL_SERVER_HOST || '0.0.0.0'
55 |
56 | /*
57 | * Optionally, apply Express middleware for authentication, etc
58 | * This also also allows us to specify a path for the GraphQL endpoint
59 | */
60 | server.applyMiddleware({ app, path })
61 |
62 | app.listen({ host, port, path }, () => {
63 | console.log(`GraphQL server ready at http://${host}:${port}${path}`)
64 | })
65 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/widgets/alert_box.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | enum AlertType {
4 | success,
5 | info,
6 | warning,
7 | error,
8 | }
9 |
10 | class AlertBox extends StatelessWidget {
11 | final String text;
12 | final AlertType type;
13 | final void Function() onRetry;
14 |
15 | const AlertBox({
16 | Key key,
17 | @required this.text,
18 | this.type = AlertType.warning,
19 | this.onRetry,
20 | }) : super(key: key);
21 |
22 | @override
23 | Widget build(BuildContext context) {
24 | IconData icon;
25 | Color color;
26 |
27 | switch (type) {
28 | case AlertType.success:
29 | {
30 | icon = Icons.check_circle;
31 | color = Colors.green;
32 | }
33 | break;
34 | case AlertType.info:
35 | {
36 | icon = Icons.info_outline;
37 | color = Colors.green;
38 | }
39 | break;
40 | case AlertType.warning:
41 | {
42 | icon = Icons.report_problem;
43 | color = Colors.amber;
44 | }
45 | break;
46 | case AlertType.error:
47 | {
48 | icon = Icons.error_outline;
49 | color = Colors.red;
50 | }
51 | }
52 |
53 | return Padding(
54 | padding: EdgeInsets.all(16.0),
55 | child: Column(
56 | children: [
57 | Row(
58 | children: [
59 | Icon(
60 | icon,
61 | color: color,
62 | size: 32.0,
63 | ),
64 | SizedBox(
65 | width: 16.0,
66 | ),
67 | Flexible(
68 | child: Text(text),
69 | ),
70 | ],
71 | ),
72 | onRetry != null ? SizedBox(height: 8.0) : SizedBox.shrink(),
73 | onRetry != null
74 | ? FlatButton(
75 | onPressed: onRetry,
76 | color: Theme.of(context).primaryColor,
77 | textColor: Theme.of(context).colorScheme.onPrimary,
78 | child: Text('Try Again'),
79 | )
80 | : SizedBox.shrink(),
81 | ],
82 | ),
83 | );
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/web-react-ts/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/screens/business_list_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:client_flutter/widgets/alert_box.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:graphql_flutter/graphql_flutter.dart';
4 | import 'package:client_flutter/model/model.dart';
5 | import 'package:client_flutter/widgets/business_list_tile.dart';
6 | import 'package:client_flutter/widgets/menu_drawer.dart';
7 |
8 | final getBusinessesQuery = gql("""
9 | query {
10 | Business {
11 | businessId,
12 | name,
13 | city,
14 | state,
15 | avgStars
16 | }
17 | }
18 | """);
19 |
20 | class BusinessListScreen extends StatelessWidget {
21 | @override
22 | Widget build(BuildContext context) {
23 | return Scaffold(
24 | appBar: AppBar(
25 | title: Text('Businesses'),
26 | ),
27 | drawer: MenuDrawer(),
28 | body: Query(
29 | options: QueryOptions(
30 | documentNode: getBusinessesQuery,
31 | ),
32 | builder: (
33 | QueryResult result, {
34 | Future Function() refetch,
35 | FetchMore fetchMore,
36 | }) {
37 | if (result.hasException) {
38 | return AlertBox(
39 | type: AlertType.error,
40 | text: result.exception.toString(),
41 | onRetry: () => refetch(),
42 | );
43 | }
44 |
45 | if (result.loading) {
46 | return const Center(
47 | child: CircularProgressIndicator(),
48 | );
49 | }
50 |
51 | final businesses = result.data['Business']
52 | .map((biz) => Business.fromJson(biz))
53 | .toList();
54 |
55 | if (businesses.length == 0) {
56 | return AlertBox(
57 | type: AlertType.info,
58 | text: 'No businesses to show.',
59 | onRetry: refetch,
60 | );
61 | }
62 |
63 | return RefreshIndicator(
64 | onRefresh: () => refetch(),
65 | child: Material(
66 | child: ListView.builder(
67 | itemBuilder: (_, index) {
68 | return BusinessListTile(business: businesses[index]);
69 | },
70 | itemCount: businesses.length,
71 | ),
72 | ),
73 | );
74 | },
75 | ),
76 | );
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/api/README.md:
--------------------------------------------------------------------------------
1 | # GRANDstack Starter - GraphQL API
2 |
3 | ## Quick Start
4 |
5 | Install dependencies:
6 |
7 | ```
8 | npm install
9 | ```
10 |
11 | Start the GraphQL service:
12 |
13 | ```
14 | npm start
15 | ```
16 |
17 | This will start the GraphQL service (by default on localhost:4000) where you can issue GraphQL requests or access GraphQL Playground in the browser:
18 |
19 | 
20 |
21 | ## Configure
22 |
23 | Set your Neo4j connection string and credentials in `.env`. For example:
24 |
25 | _.env_
26 |
27 | ```
28 | NEO4J_URI=bolt://localhost:7687
29 | NEO4J_USER=neo4j
30 | NEO4J_PASSWORD=letmein
31 | AUTH_DIRECTIVES_ROLE_KEY=https:///role
32 | JWT_SECRET="-----BEGIN PUBLIC KEY----------END PUBLIC KEY-----"
33 | ```
34 |
35 | 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`.
36 |
37 | ## Configuring Auth0 for GRANDstack - GraphQL.
38 |
39 | Please read this [write-up](auth0-howto.md) for more information on configuring `Auth0` for GRANDStack - GraphQL.
40 |
41 |
42 | ## Deployment
43 |
44 | You can deploy to any service that hosts Node.js apps, but [Vercel](https://vercel.com/home) is a great easy to use service for hosting your app that has an easy to use free plan for small projects.
45 |
46 | To deploy your GraphQL service on Zeit Now, first install [Vercel](https://vercel.com/download) - you'll need to provide an email address. Then run
47 |
48 | ```
49 | vercel
50 | ```
51 |
52 | to deploy your GraphQL service on Vercel. 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.vercel.sh/
53 |
54 | ## Seeding The Database
55 |
56 | Optionally you can seed the GraphQL service by executing mutations that will write sample data to the database:
57 |
58 | ```
59 | npm run seedDb
60 | ```
61 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/web-react-ts/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/mobile_client_flutter/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
19 |
23 |
27 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
46 |
47 |
--------------------------------------------------------------------------------
/api/src/seed/seed-mutations.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch')
2 | const parse = require('csv-parse/lib/sync')
3 | const { gql } = require('@apollo/client')
4 |
5 | export const getSeedMutations = async () => {
6 | const res = await fetch(
7 | 'https://cdn.neo4jlabs.com/data/grandstack_businesses.csv'
8 | )
9 | const body = await res.text()
10 | const records = parse(body, { columns: true })
11 | const mutations = generateMutations(records)
12 |
13 | return mutations
14 | }
15 |
16 | const generateMutations = (records) => {
17 | return records.map((rec) => {
18 | Object.keys(rec).map((k) => {
19 | if (k === 'latitude' || k === 'longitude' || k === 'reviewStars') {
20 | rec[k] = parseFloat(rec[k])
21 | } else if (k === 'reviewDate') {
22 | const dateParts = rec[k].split('-')
23 | rec['year'] = parseInt(dateParts[0])
24 | rec['month'] = parseInt(dateParts[1])
25 | rec['day'] = parseInt(dateParts[2])
26 | } else if (k === 'categories') {
27 | rec[k] = rec[k].split(',')
28 | }
29 | })
30 |
31 | return {
32 | mutation: gql`
33 | mutation mergeReviews(
34 | $userId: ID!
35 | $userName: String!
36 | $businessId: ID!
37 | $businessName: String!
38 | $businessCity: String!
39 | $businessState: String!
40 | $businessAddress: String!
41 | $latitude: Float!
42 | $longitude: Float!
43 | $reviewId: ID!
44 | $reviewText: String
45 | $reviewDate: DateTime
46 | $reviewStars: Float
47 | $categories: [String!]!
48 | ) {
49 | user: mergeUser(userId: $userId, name: $userName) {
50 | userId
51 | }
52 | business: mergeBusiness(
53 | businessId: $businessId
54 | name: $businessName
55 | address: $businessAddress
56 | city: $businessCity
57 | state: $businessState
58 | latitude: $latitude
59 | longitude: $longitude
60 | ) {
61 | businessId
62 | }
63 |
64 | businessCategories: mergeBusinessCategory(
65 | categories: $categories
66 | businessId: $businessId
67 | ) {
68 | businessId
69 | }
70 |
71 | reviews: createReviews(
72 | input: {
73 | reviewId: $reviewId
74 | stars: $reviewStars
75 | text: $reviewText
76 | date: $reviewDate
77 | business: { connect: { where: { businessId: $businessId } } }
78 | user: { connect: { where: { userId: $userId } } }
79 | }
80 | ) {
81 | reviews {
82 | reviewId
83 | date
84 | }
85 | }
86 | }
87 | `,
88 | variables: rec,
89 | }
90 | })
91 | }
92 |
--------------------------------------------------------------------------------
/web-angular/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
22 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
23 |
24 | /**
25 | * Web Animations `@angular/platform-browser/animations`
26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28 | */
29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
30 |
31 | /**
32 | * By default, zone.js will patch all possible macroTask and DomEvents
33 | * user can disable parts of macroTask/DomEvents patch by setting following flags
34 | * because those flags need to be set before `zone.js` being loaded, and webpack
35 | * will put import in the top of bundle, so user need to create a separate file
36 | * in this directory (for example: zone-flags.ts), and put the following flags
37 | * into that file, and then add the following code before importing zone.js.
38 | * import './zone-flags.ts';
39 | *
40 | * The flags allowed in zone-flags.ts are listed here.
41 | *
42 | * The following flags will work for all browsers.
43 | *
44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47 | *
48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
50 | *
51 | * (window as any).__Zone_enable_cross_context_check = true;
52 | *
53 | */
54 |
55 | /***************************************************************************************************
56 | * Zone JS is required by default for Angular itself.
57 | */
58 | import 'zone.js/dist/zone'; // Included with Angular CLI.
59 |
60 |
61 | /***************************************************************************************************
62 | * APPLICATION IMPORTS
63 | */
64 |
--------------------------------------------------------------------------------
/api/src/schema.graphql:
--------------------------------------------------------------------------------
1 | scalar Point
2 | scalar DateTime
3 | scalar PointInput
4 |
5 | type User {
6 | userId: ID!
7 | name: String
8 | reviews: [Review] @relationship(type: "WROTE", direction: OUT)
9 | avgStars: Float
10 | @cypher(
11 | statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN toFloat(avg(r.stars))"
12 | )
13 | numReviews: Int
14 | @cypher(statement: "MATCH (this)-[:WROTE]->(r:Review) RETURN COUNT(r)")
15 | recommendations(first: Int = 3): [Business]
16 | @cypher(
17 | 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"
18 | )
19 | }
20 |
21 | type Business {
22 | businessId: ID!
23 | name: String!
24 | address: String
25 | city: String
26 | state: String
27 | location: Point
28 | avgStars: Float
29 | @cypher(
30 | statement: "MATCH (this)<-[:REVIEWS]-(r:Review) RETURN coalesce(avg(r.stars),0.0)"
31 | )
32 | reviews: [Review] @relationship(type: "REVIEWS", direction: IN)
33 | categories: [Category] @relationship(type: "IN_CATEGORY", direction: OUT)
34 | }
35 |
36 | type Review {
37 | reviewId: ID!
38 | stars: Float
39 | text: String
40 | date: DateTime
41 | business: Business @relationship(type: "REVIEWS", direction: OUT)
42 | user: User @relationship(type: "WROTE", direction: IN)
43 | }
44 |
45 | type Category {
46 | name: ID!
47 | businesses: [Business] @relationship(type: "IN_CATEGORY", direction: IN)
48 | }
49 |
50 | type RatingCount @exclude {
51 | stars: Float!
52 | count: Int!
53 | }
54 |
55 | type Mutation {
56 | mergeBusinessCategory(categories: [String!]!, businessId: ID!): Business
57 | @cypher(
58 | statement: "MATCH (b:Business {businessId: $businessId}) UNWIND $categories AS cat MERGE (c:Category {name: cat}) MERGE (b)-[:IN_CATEGORY]->(c) RETURN b"
59 | )
60 | mergeUser(name: String!, userId: ID!): User
61 | @cypher(
62 | statement: """
63 | MERGE (u:User {userId: $userId})
64 | ON CREATE SET u.name = $name
65 | RETURN u
66 | """
67 | )
68 | mergeBusiness(
69 | businessId: ID!
70 | name: String!
71 | address: String!
72 | city: String!
73 | state: String!
74 | latitude: Float!
75 | longitude: Float!
76 | ): Business
77 | @cypher(
78 | statement: """
79 | MERGE (b:Business {businessId: $businessId})
80 | ON CREATE SET b.name = $name,
81 | b.address = $address,
82 | b.city = $city,
83 | b.state = $state,
84 | b.location = Point({latitude: $latitude, longitude: $longitude})
85 | RETURN b
86 | """
87 | )
88 | }
89 |
90 | type Query {
91 | userCount: Int! @cypher(statement: "MATCH (u:User) RETURN COUNT(u)")
92 | ratingsCount: [RatingCount]
93 | @cypher(
94 | statement: "MATCH (r:Review) WITH r.stars AS stars, COUNT(*) AS count ORDER BY stars RETURN {stars: stars, count: count}"
95 | )
96 | }
97 |
--------------------------------------------------------------------------------
/web-angular/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ng-client": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss"
11 | }
12 | },
13 | "root": "",
14 | "sourceRoot": "src",
15 | "prefix": "app",
16 | "architect": {
17 | "build": {
18 | "builder": "@angular-devkit/build-angular:browser",
19 | "options": {
20 | "outputPath": "dist/ng-client",
21 | "index": "src/index.html",
22 | "main": "src/main.ts",
23 | "polyfills": "src/polyfills.ts",
24 | "tsConfig": "tsconfig.app.json",
25 | "aot": false,
26 | "assets": [
27 | "src/favicon.ico",
28 | "src/assets"
29 | ],
30 | "styles": [
31 | "node_modules/@clr/icons/clr-icons.min.css",
32 | "node_modules/@clr/ui/clr-ui.min.css",
33 | "src/styles.scss"
34 | ],
35 | "scripts": [
36 | "node_modules/@webcomponents/custom-elements/custom-elements.min.js",
37 | "node_modules/@clr/icons/clr-icons.min.js"
38 | ]
39 | },
40 | "configurations": {
41 | "production": {
42 | "fileReplacements": [
43 | {
44 | "replace": "src/environments/environment.ts",
45 | "with": "src/environments/environment.prod.ts"
46 | }
47 | ],
48 | "optimization": true,
49 | "outputHashing": "all",
50 | "sourceMap": false,
51 | "extractCss": true,
52 | "namedChunks": false,
53 | "aot": true,
54 | "extractLicenses": true,
55 | "vendorChunk": false,
56 | "buildOptimizer": true,
57 | "budgets": [
58 | {
59 | "type": "initial",
60 | "maximumWarning": "2mb",
61 | "maximumError": "5mb"
62 | },
63 | {
64 | "type": "anyComponentStyle",
65 | "maximumWarning": "6kb",
66 | "maximumError": "10kb"
67 | }
68 | ]
69 | }
70 | }
71 | },
72 | "serve": {
73 | "builder": "@angular-devkit/build-angular:dev-server",
74 | "options": {
75 | "browserTarget": "ng-client:build"
76 | },
77 | "configurations": {
78 | "production": {
79 | "browserTarget": "ng-client:build:production"
80 | }
81 | }
82 | },
83 | "extract-i18n": {
84 | "builder": "@angular-devkit/build-angular:extract-i18n",
85 | "options": {
86 | "browserTarget": "ng-client:build"
87 | }
88 | }
89 | }
90 | }
91 | },
92 | "defaultProject": "ng-client"
93 | }
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/screens/user_list_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:client_flutter/widgets/alert_box.dart';
2 | import 'package:flutter/material.dart';
3 | import 'package:graphql_flutter/graphql_flutter.dart';
4 | import 'package:client_flutter/model/model.dart';
5 | import 'package:client_flutter/widgets/menu_drawer.dart';
6 | import 'user_detail_screen.dart';
7 |
8 | final getUsersQuery = gql("""
9 | query {
10 | User {
11 | userId,
12 | name,
13 | numReviews
14 | }
15 | }
16 | """);
17 |
18 | class UserListScreen extends StatelessWidget {
19 | @override
20 | Widget build(BuildContext context) {
21 | return Scaffold(
22 | appBar: AppBar(
23 | title: Text('Users'),
24 | ),
25 | drawer: MenuDrawer(),
26 | body: Query(
27 | options: QueryOptions(
28 | documentNode: getUsersQuery,
29 | ),
30 | builder: (
31 | QueryResult result, {
32 | Future Function() refetch,
33 | FetchMore fetchMore,
34 | }) {
35 | if (result.hasException) {
36 | return AlertBox(
37 | type: AlertType.error,
38 | text: result.exception.toString(),
39 | onRetry: () => refetch(),
40 | );
41 | }
42 |
43 | if (result.loading) {
44 | return const Center(
45 | child: CircularProgressIndicator(),
46 | );
47 | }
48 |
49 | final List users = result.data['User']
50 | .map((user) => User.fromJson(user))
51 | .toList();
52 |
53 | if (users.length == 0) {
54 | return AlertBox(
55 | type: AlertType.info,
56 | text: 'No users to show.',
57 | onRetry: refetch,
58 | );
59 | }
60 |
61 | return RefreshIndicator(
62 | onRefresh: () => refetch(),
63 | child: Material(
64 | child: ListView.builder(
65 | itemBuilder: (_, index) {
66 | return ListTile(
67 | leading: CircleAvatar(
68 | backgroundColor: Theme.of(context).primaryColorLight,
69 | child: Text(
70 | users[index].name.substring(0, 1),
71 | style: TextStyle(fontWeight: FontWeight.w500),
72 | ),
73 | ),
74 | title: Text(users[index].name),
75 | trailing: Text('${users[index].numReviews} reviews'),
76 | onTap: () {
77 | final userId = users[index].userId;
78 | Navigator.push(
79 | context,
80 | MaterialPageRoute(
81 | builder: (context) =>
82 | UserDetailScreen(userId: userId),
83 | ),
84 | );
85 | },
86 | );
87 | },
88 | itemCount: users.length,
89 | ),
90 | ),
91 | );
92 | },
93 | ),
94 | );
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/model/model.dart:
--------------------------------------------------------------------------------
1 | import 'package:json_annotation/json_annotation.dart';
2 | part 'model.g.dart';
3 |
4 | @JsonSerializable()
5 | class Neo4jDate {
6 | int year;
7 | int month;
8 | int day;
9 | String formatted;
10 |
11 | Neo4jDate({this.year, this.month, this.day, this.formatted});
12 |
13 | factory Neo4jDate.fromJson(Map json) =>
14 | _$Neo4jDateFromJson(json);
15 | Map toJson() => _$Neo4jDateToJson(this);
16 | }
17 |
18 | @JsonSerializable()
19 | class Neo4jPoint {
20 | double latitude;
21 | double longitude;
22 |
23 | Neo4jPoint({this.latitude, this.longitude});
24 |
25 | factory Neo4jPoint.fromJson(Map json) =>
26 | _$Neo4jPointFromJson(json);
27 | Map toJson() => _$Neo4jPointToJson(this);
28 | }
29 |
30 | @JsonSerializable()
31 | class User {
32 | String userId;
33 | String name;
34 | List reviews;
35 | double avgStars;
36 | int numReviews;
37 | List recommendations;
38 |
39 | User(
40 | {this.userId,
41 | this.name,
42 | this.reviews,
43 | this.avgStars,
44 | this.numReviews,
45 | this.recommendations});
46 |
47 | factory User.fromJson(Map json) => _$UserFromJson(json);
48 | Map toJson() => _$UserToJson(this);
49 | }
50 |
51 | @JsonSerializable()
52 | class Business {
53 | String businessId;
54 | String name;
55 | String address;
56 | String city;
57 | String state;
58 | Neo4jPoint location;
59 | double avgStars;
60 | List reviews;
61 | List categories;
62 |
63 | Business(
64 | {this.businessId,
65 | this.name,
66 | this.address,
67 | this.city,
68 | this.state,
69 | this.location,
70 | this.avgStars,
71 | this.reviews,
72 | this.categories});
73 |
74 | factory Business.fromJson(Map json) =>
75 | _$BusinessFromJson(json);
76 | Map toJson() => _$BusinessToJson(this);
77 | }
78 |
79 | @JsonSerializable()
80 | class Review {
81 | String reviewId;
82 | double stars;
83 | String text;
84 | Neo4jDate date;
85 | Business business;
86 | User user;
87 |
88 | Review({
89 | this.reviewId,
90 | this.stars,
91 | this.text,
92 | this.date,
93 | this.business,
94 | this.user,
95 | });
96 |
97 | factory Review.fromJson(Map json) => _$ReviewFromJson(json);
98 | Map toJson() => _$ReviewToJson(this);
99 | }
100 |
101 | @JsonSerializable()
102 | class Category {
103 | String name;
104 | List businesses;
105 |
106 | Category({this.name, this.businesses});
107 |
108 | factory Category.fromJson(Map json) =>
109 | _$CategoryFromJson(json);
110 | Map toJson() => _$CategoryToJson(this);
111 | }
112 |
113 | @JsonSerializable()
114 | class RatingCount {
115 | double stars;
116 | int count;
117 |
118 | RatingCount({this.stars, this.count});
119 |
120 | factory RatingCount.fromJson(Map json) =>
121 | _$RatingCountFromJson(json);
122 | Map toJson() => _$RatingCountToJson(this);
123 | }
124 |
--------------------------------------------------------------------------------
/mobile_client_flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/api/auth0-howto.md:
--------------------------------------------------------------------------------
1 | ## To-Do: Automate getting JWT_SECRET and AUTH_DIRECTIVES_ROLE_KEY
2 | It is possible to automate the below steps. However, Below are manual steps listed.
3 |
4 |
5 | ## Auth0 for Grand Stack in
6 | 1. Navigate to `https://manage.auth0.com/dashboard/`. If you do not have an auth0 account, create one under the free plan.
7 | 2. Create a new application on Auth0 (Singple Page Applications - SPA). If you have already created an application on Auth0 for GRANDstack, Skip this step.
8 | 3. For `AUTH_DIRECTIVES_ROLE_KEY`, Under the application settings, Copy value of `Domain` under `basic information`. the domain information `https:///roles`
9 | 4. Visit settings under this application, Scroll to the bottom and select `Show Advanced Settings`.
10 | 5. `Advanced Settings` has various tabs. Under the tab named `Certificates`, copy the text in the box for `Signing Certificate`. This is our `JWT_SECRET`.
11 | 6. Ensure that you add CRLF to the `JWT_SECRET` as we need this to be a string. Please Check the Instance below for reference only.
12 |
13 | ## Explaining AUTH_DIRECTIVES_ROLE_KEY
14 | 1. Auth0 - Roles, Permissions and Scopes are sent to a user's accessToken (JWT).
15 | 2. If you are using `auth0-spajs` or `auth-reactjs`, JWT can be received with getTokenSilently.
16 | Snippet: `const accessToken = await auth0Client.getTokenSilently();`
17 | 3. Decode the token on jwt.io. You will notice that a key that looks `AUTH_DIRECTIVES_ROLE_KEY` contains roles, permissions and scopes as a array.
18 | 4. In-Short, AUTH_DIRECTIVES_ROLE_KEY helps us to automatically tell `graphql` to use this key for roles.
19 |
20 |
21 | ## Instance - JWT_SECRET
22 | Instance: Signing certificate may look like this.
23 | ```
24 | -----BEGIN CERTIFICATE-----
25 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
26 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
27 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
28 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
29 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
30 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
31 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
32 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
33 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
34 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
35 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
36 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
37 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
38 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
39 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
40 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
41 | xxxxxxxxxxxxxxxxxxxx
42 | -----END CERTIFICATE-----
43 | ```
44 |
45 | Convert that to:
46 | ```-----BEGIN CERTIFICATE----\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n-----END CERTIFICATE-----```
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to GraphQl Architect
2 |
3 | ## Table of Contents
4 |
5 | - [Code of Conduct](#code-of-conduct)
6 | - [Contributor License Agreement](#contributor-license-agreement)
7 | - [Code Organization](#code-organization)
8 | - [Setting Up the project locally](#setting-up-the-project-locally)
9 | - [Local Development for Desktop](#local-development-for-desktop)
10 | - [Submitting a Pull Request](#submitting-a-pull-request)
11 | - [Add yourself as a contributor](#add-yourself-as-a-contributor)
12 |
13 | ## Code of Conduct
14 |
15 | We have a code of conduct you can find [here](./CODE_OF_CONDUCT.md) and every
16 | contributor is expected to obey the rules therein. Any issues or PRs that don't
17 | abide by the code of conduct may be closed.
18 |
19 | ## Code Organization
20 |
21 | Check out the [overview in readme](./CONTRIBUTING#overview) for the structure of the project.
22 |
23 | **Working on your first Pull Request?** You can learn how from this _free_
24 | series
25 | [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
26 |
27 | ## Setting Up the project locally
28 |
29 | To install the project you need to have `node` and `npm` installed
30 |
31 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone
32 | your fork:
33 |
34 | ```sh
35 | # Clone your fork
36 | git clone https://github.com//graphql-architect.git
37 |
38 | # Navigate to the newly cloned directory
39 | cd graphql-architect
40 | ```
41 |
42 | 2. from the root of the project: `npm install` all dependencies
43 | - make sure you have latest `npm` version
44 | 3. from the root of the project: `npm start`
45 |
46 | > Tip: Keep your `master` branch pointing at the original repository and make
47 | > pull requests from branches on your fork. To do this, run:
48 | >
49 | > ```sh
50 | > git remote add upstream https://github.com/grand-stack/graphql-architect
51 | > git fetch upstream
52 | > git branch --set-upstream-to=upstream/master master
53 | > ```
54 | >
55 | > This will add the original repository as a "remote" called "upstream," then
56 | > fetch the git information from that remote, then set your local `master`
57 | > branch to use the upstream master branch whenever you run `git pull`. Then you
58 | > can make all of your pull request branches based on this `master` branch.
59 | > Whenever you want to update your version of `master`, do a regular `git pull`.
60 |
61 | ## Local Development
62 |
63 | Grandstack Architect can be built and developed one of two ways: Through the web app process and
64 | into a package that can be added directly to neo4j desktop. While the web app portion works in an
65 | aesthetic/theoretical sense most of the complexity and usability is currently focused on the built
66 | application.
67 |
68 | ### Building for Desktop
69 |
70 | To build the app and test it in development in desktop you will need to
71 |
72 | 1. Install [Neo4j Desktop](https://neo4j.com/download/)
73 | 2. Pack the app with `npm run pack:dev` or `npm run pack`
74 | 3. Drag and drop the packed tarball into Neo4j Desktop
75 |
76 | ### Running the Web App
77 |
78 | ```sh
79 | $ npm run start
80 | ```
81 |
82 | ## Submitting a Pull Request
83 |
84 | Please go through existing issues and pull requests to check if somebody else is
85 | already working on it, we use `someone working on it` label to mark such issues.
86 |
87 | `husky` will run type checking, prettier and a linter before you are allowed to commit
88 | but you may run these commands seperately
89 |
90 | ```sh
91 | npm test
92 | npm run lint
93 | npm run format
94 | ```
95 |
96 | ## Add yourself as a contributor
97 |
98 | This project follows the
99 | [all-contributors](https://github.com/all-contributors/all-contributors)
100 | specification. Contributions of any kind welcome!
101 |
--------------------------------------------------------------------------------
/web-react/src/components/UserList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useQuery, gql } from '@apollo/client'
3 | import { withStyles } from '@material-ui/core/styles'
4 | import {
5 | Table,
6 | TableBody,
7 | TableCell,
8 | TableHead,
9 | TableRow,
10 | Tooltip,
11 | Paper,
12 | TableSortLabel,
13 | TextField,
14 | } from '@material-ui/core'
15 |
16 | import Title from './Title'
17 |
18 | const styles = (theme) => ({
19 | root: {
20 | maxWidth: 700,
21 | marginTop: theme.spacing(3),
22 | overflowX: 'auto',
23 | margin: 'auto',
24 | },
25 | table: {
26 | minWidth: 700,
27 | },
28 | textField: {
29 | marginLeft: theme.spacing(1),
30 | marginRight: theme.spacing(1),
31 | minWidth: 300,
32 | },
33 | })
34 |
35 | const GET_USER = gql`
36 | query usersPaginateQuery(
37 | $first: Int
38 | $offset: Int
39 | $orderBy: [UserSort]
40 | $filter: UserWhere
41 | ) {
42 | users(
43 | options: { limit: $first, skip: $offset, sort: $orderBy }
44 | where: $filter
45 | ) {
46 | id: userId
47 | name
48 | avgStars
49 | numReviews
50 | }
51 | }
52 | `
53 |
54 | function UserList(props) {
55 | const { classes } = props
56 | const [order, setOrder] = React.useState('ASC')
57 | const [orderBy, setOrderBy] = React.useState('name')
58 | const [page] = React.useState(0)
59 | const [rowsPerPage] = React.useState(10)
60 | const [filterState, setFilterState] = React.useState({ usernameFilter: '' })
61 |
62 | const getFilter = () => {
63 | return filterState.usernameFilter.length > 0
64 | ? { name_CONTAINS: filterState.usernameFilter }
65 | : {}
66 | }
67 |
68 | const { loading, data, error } = useQuery(GET_USER, {
69 | variables: {
70 | first: rowsPerPage,
71 | offset: rowsPerPage * page,
72 | orderBy: { [orderBy]: order },
73 | filter: getFilter(),
74 | },
75 | })
76 |
77 | const handleSortRequest = (property) => {
78 | const newOrderBy = property
79 | let newOrder = 'DESC'
80 |
81 | if (orderBy === property && order === 'DESC') {
82 | newOrder = 'ASC'
83 | }
84 |
85 | setOrder(newOrder)
86 | setOrderBy(newOrderBy)
87 | }
88 |
89 | const handleFilterChange = (filterName) => (event) => {
90 | const val = event.target.value
91 |
92 | setFilterState((oldFilterState) => ({
93 | ...oldFilterState,
94 | [filterName]: val,
95 | }))
96 | }
97 |
98 | return (
99 |
100 | User List
101 |
114 | {loading && !error && Loading...
}
115 | {error && !loading && Error
}
116 | {data && !loading && !error && (
117 |
118 |
119 |
120 |
124 |
125 | handleSortRequest('name')}
129 | >
130 | Name
131 |
132 |
133 |
134 | Average Stars
135 | Number of Reviews
136 |
137 |
138 |
139 | {data.users.map((n) => {
140 | return (
141 |
142 |
143 | {n.name}
144 |
145 |
146 | {n.avgStars ? n.avgStars.toFixed(2) : '-'}
147 |
148 | {n.numReviews}
149 |
150 | )
151 | })}
152 |
153 |
154 | )}
155 |
156 | )
157 | }
158 |
159 | export default withStyles(styles)(UserList)
160 |
--------------------------------------------------------------------------------
/web-react/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | )
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl)
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | )
46 | })
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl)
50 | }
51 | })
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then((registration) => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.')
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.')
74 | }
75 | }
76 | }
77 | }
78 | })
79 | .catch((error) => {
80 | console.error('Error during service worker registration:', error)
81 | })
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then((response) => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then((registration) => {
95 | registration.unregister().then(() => {
96 | window.location.reload()
97 | })
98 | })
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl)
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | )
108 | })
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then((registration) => {
114 | registration.unregister()
115 | })
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/web-react-ts/src/components/UserList.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withStyles, createStyles, Theme } from '@material-ui/core/styles'
3 | import {
4 | Table,
5 | TableBody,
6 | TableCell,
7 | SortDirection,
8 | TableHead,
9 | TableRow,
10 | Tooltip,
11 | Paper,
12 | TableSortLabel,
13 | TextField,
14 | } from '@material-ui/core'
15 | import { useQuery, gql } from '@apollo/client'
16 |
17 | import Title from './Title'
18 |
19 | const styles = (theme: Theme) =>
20 | createStyles({
21 | root: {
22 | maxWidth: 700,
23 | marginTop: theme.spacing(3),
24 | overflowX: 'auto',
25 | margin: 'auto',
26 | },
27 | table: {
28 | minWidth: 700,
29 | },
30 | textField: {
31 | marginLeft: theme.spacing(1),
32 | marginRight: theme.spacing(1),
33 | minWidth: 300,
34 | },
35 | })
36 |
37 | const GET_USER = gql`
38 | query usersPaginateQuery(
39 | $first: Int
40 | $offset: Int
41 | $orderBy: [UserSort]
42 | $filter: UserWhere
43 | ) {
44 | users(
45 | options: { limit: $first, skip: $offset, sort: $orderBy }
46 | where: $filter
47 | ) {
48 | id: userId
49 | name
50 | avgStars
51 | numReviews
52 | }
53 | }
54 | `
55 |
56 | function UserList(props: any) {
57 | const { classes } = props
58 | const [order, setOrder] = React.useState<'asc' | 'desc'>('asc')
59 | const [orderBy, setOrderBy] = React.useState('name')
60 | const [page] = React.useState(0)
61 | const [rowsPerPage] = React.useState(10)
62 | const [filterState, setFilterState] = React.useState({ usernameFilter: '' })
63 |
64 | const getFilter = () => {
65 | return filterState.usernameFilter.length > 0
66 | ? { name_CONTAINS: filterState.usernameFilter }
67 | : {}
68 | }
69 |
70 | const { loading, data, error } = useQuery(GET_USER, {
71 | variables: {
72 | first: rowsPerPage,
73 | offset: rowsPerPage * page,
74 | orderBy: { [orderBy]: order.toUpperCase() },
75 | filter: getFilter(),
76 | },
77 | })
78 |
79 | const handleSortRequest = (property: any) => {
80 | const newOrderBy = property
81 | let newOrder: SortDirection = 'desc'
82 |
83 | if (orderBy === property && order === 'desc') {
84 | newOrder = 'asc'
85 | }
86 |
87 | setOrder(newOrder)
88 | setOrderBy(newOrderBy)
89 | }
90 |
91 | const handleFilterChange = (filterName: any) => (event: any) => {
92 | const val = event.target.value
93 |
94 | setFilterState((oldFilterState) => ({
95 | ...oldFilterState,
96 | [filterName]: val,
97 | }))
98 | }
99 |
100 | return (
101 |
102 | User List
103 |
116 | {loading && !error && Loading...
}
117 | {error && !loading && Error
}
118 | {data && !loading && !error && (
119 |
120 |
121 |
122 |
126 |
127 | handleSortRequest('name')}
131 | >
132 | Name
133 |
134 |
135 |
136 |
140 | Average Stars
141 |
142 |
146 | Number of Reviews
147 |
148 |
149 |
150 |
151 | {data.users.map((n: any) => {
152 | return (
153 |
154 |
155 | {n.name}
156 |
157 |
158 | {n.avgStars ? n.avgStars.toFixed(2) : '-'}
159 |
160 | {n.numReviews}
161 |
162 | )
163 | })}
164 |
165 |
166 | )}
167 |
168 | )
169 | }
170 |
171 | export default withStyles(styles)(UserList)
172 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/model/model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | Neo4jDate _$Neo4jDateFromJson(Map json) {
10 | return Neo4jDate(
11 | year: json['year'] as int,
12 | month: json['month'] as int,
13 | day: json['day'] as int,
14 | formatted: json['formatted'] as String,
15 | );
16 | }
17 |
18 | Map _$Neo4jDateToJson(Neo4jDate instance) => {
19 | 'year': instance.year,
20 | 'month': instance.month,
21 | 'day': instance.day,
22 | 'formatted': instance.formatted,
23 | };
24 |
25 | Neo4jPoint _$Neo4jPointFromJson(Map json) {
26 | return Neo4jPoint(
27 | latitude: (json['latitude'] as num)?.toDouble(),
28 | longitude: (json['longitude'] as num)?.toDouble(),
29 | );
30 | }
31 |
32 | Map _$Neo4jPointToJson(Neo4jPoint instance) =>
33 | {
34 | 'latitude': instance.latitude,
35 | 'longitude': instance.longitude,
36 | };
37 |
38 | User _$UserFromJson(Map json) {
39 | return User(
40 | userId: json['userId'] as String,
41 | name: json['name'] as String,
42 | reviews: (json['reviews'] as List)
43 | ?.map((e) =>
44 | e == null ? null : Review.fromJson(e as Map))
45 | ?.toList(),
46 | avgStars: (json['avgStars'] as num)?.toDouble(),
47 | numReviews: json['numReviews'] as int,
48 | recommendations: (json['recommendations'] as List)
49 | ?.map((e) =>
50 | e == null ? null : Business.fromJson(e as Map))
51 | ?.toList(),
52 | );
53 | }
54 |
55 | Map _$UserToJson(User instance) => {
56 | 'userId': instance.userId,
57 | 'name': instance.name,
58 | 'reviews': instance.reviews,
59 | 'avgStars': instance.avgStars,
60 | 'numReviews': instance.numReviews,
61 | 'recommendations': instance.recommendations,
62 | };
63 |
64 | Business _$BusinessFromJson(Map json) {
65 | return Business(
66 | businessId: json['businessId'] as String,
67 | name: json['name'] as String,
68 | address: json['address'] as String,
69 | city: json['city'] as String,
70 | state: json['state'] as String,
71 | location: json['location'] == null
72 | ? null
73 | : Neo4jPoint.fromJson(json['location'] as Map),
74 | avgStars: (json['avgStars'] as num)?.toDouble(),
75 | reviews: (json['reviews'] as List)
76 | ?.map((e) =>
77 | e == null ? null : Review.fromJson(e as Map))
78 | ?.toList(),
79 | categories: (json['categories'] as List)
80 | ?.map((e) =>
81 | e == null ? null : Category.fromJson(e as Map))
82 | ?.toList(),
83 | );
84 | }
85 |
86 | Map _$BusinessToJson(Business instance) => {
87 | 'businessId': instance.businessId,
88 | 'name': instance.name,
89 | 'address': instance.address,
90 | 'city': instance.city,
91 | 'state': instance.state,
92 | 'location': instance.location,
93 | 'avgStars': instance.avgStars,
94 | 'reviews': instance.reviews,
95 | 'categories': instance.categories,
96 | };
97 |
98 | Review _$ReviewFromJson(Map json) {
99 | return Review(
100 | reviewId: json['reviewId'] as String,
101 | stars: (json['stars'] as num)?.toDouble(),
102 | text: json['text'] as String,
103 | date: json['date'] == null
104 | ? null
105 | : Neo4jDate.fromJson(json['date'] as Map),
106 | business: json['business'] == null
107 | ? null
108 | : Business.fromJson(json['business'] as Map),
109 | user: json['user'] == null
110 | ? null
111 | : User.fromJson(json['user'] as Map),
112 | );
113 | }
114 |
115 | Map _$ReviewToJson(Review instance) => {
116 | 'reviewId': instance.reviewId,
117 | 'stars': instance.stars,
118 | 'text': instance.text,
119 | 'date': instance.date,
120 | 'business': instance.business,
121 | 'user': instance.user,
122 | };
123 |
124 | Category _$CategoryFromJson(Map json) {
125 | return Category(
126 | name: json['name'] as String,
127 | businesses: (json['businesses'] as List)
128 | ?.map((e) =>
129 | e == null ? null : Business.fromJson(e as Map))
130 | ?.toList(),
131 | );
132 | }
133 |
134 | Map _$CategoryToJson(Category instance) => {
135 | 'name': instance.name,
136 | 'businesses': instance.businesses,
137 | };
138 |
139 | RatingCount _$RatingCountFromJson(Map json) {
140 | return RatingCount(
141 | stars: (json['stars'] as num)?.toDouble(),
142 | count: json['count'] as int,
143 | );
144 | }
145 |
146 | Map _$RatingCountToJson(RatingCount instance) =>
147 | {
148 | 'stars': instance.stars,
149 | 'count': instance.count,
150 | };
151 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders.
63 | All complaints will be reviewed and investigated promptly and fairly.
64 |
65 | All community leaders are obligated to respect the privacy and security of the
66 | reporter of any incident.
67 |
68 | ## Enforcement Guidelines
69 |
70 | Community leaders will follow these Community Impact Guidelines in determining
71 | the consequences for any action they deem in violation of this Code of Conduct:
72 |
73 | ### 1. Correction
74 |
75 | **Community Impact**: Use of inappropriate language or other behavior deemed
76 | unprofessional or unwelcome in the community.
77 |
78 | **Consequence**: A private, written warning from community leaders, providing
79 | clarity around the nature of the violation and an explanation of why the
80 | behavior was inappropriate. A public apology may be requested.
81 |
82 | ### 2. Warning
83 |
84 | **Community Impact**: A violation through a single incident or series
85 | of actions.
86 |
87 | **Consequence**: A warning with consequences for continued behavior. No
88 | interaction with the people involved, including unsolicited interaction with
89 | those enforcing the Code of Conduct, for a specified period of time. This
90 | includes avoiding interactions in community spaces as well as external channels
91 | like social media. Violating these terms may lead to a temporary or
92 | permanent ban.
93 |
94 | ### 3. Temporary Ban
95 |
96 | **Community Impact**: A serious violation of community standards, including
97 | sustained inappropriate behavior.
98 |
99 | **Consequence**: A temporary ban from any sort of interaction or public
100 | communication with the community for a specified period of time. No public or
101 | private interaction with the people involved, including unsolicited interaction
102 | with those enforcing the Code of Conduct, is allowed during this period.
103 | Violating these terms may lead to a permanent ban.
104 |
105 | ### 4. Permanent Ban
106 |
107 | **Community Impact**: Demonstrating a pattern of violation of community
108 | standards, including sustained inappropriate behavior, harassment of an
109 | individual, or aggression toward or disparagement of classes of individuals.
110 |
111 | **Consequence**: A permanent ban from any sort of public interaction within
112 | the community.
113 |
114 | ## Attribution
115 |
116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117 | version 2.0, available at
118 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
119 |
120 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
121 | enforcement ladder](https://github.com/mozilla/diversity).
122 |
123 | [homepage]: https://www.contributor-covenant.org
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | https://www.contributor-covenant.org/faq. Translations are available at
127 | https://www.contributor-covenant.org/translations.
128 |
--------------------------------------------------------------------------------
/web-react-ts/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/mobile_client_flutter/lib/screens/business_detail_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:graphql_flutter/graphql_flutter.dart';
3 | import 'package:client_flutter/model/model.dart';
4 | import 'package:client_flutter/screens/user_detail_screen.dart';
5 | import 'package:client_flutter/widgets/alert_box.dart';
6 | import 'package:client_flutter/widgets/rating_display.dart';
7 |
8 | final businessByIdQuery = gql("""
9 | query businessById(\$id: ID) {
10 | Business(businessId: \$id) {
11 | businessId,
12 | name,
13 | address,
14 | city,
15 | state,
16 | avgStars,
17 | categories {
18 | name
19 | }
20 | reviews {
21 | stars,
22 | text,
23 | date {
24 | year
25 | month
26 | day
27 | },
28 | user {
29 | userId
30 | name
31 | }
32 | }
33 | }
34 | }
35 | """);
36 |
37 | class BusinessDetailScreen extends StatelessWidget {
38 | final String businessId;
39 |
40 | const BusinessDetailScreen({Key key, @required this.businessId})
41 | : super(key: key);
42 |
43 | @override
44 | Widget build(BuildContext context) {
45 | return Query(
46 | options: QueryOptions(
47 | documentNode: businessByIdQuery,
48 | variables: {
49 | 'id': businessId,
50 | },
51 | ),
52 | builder: (
53 | QueryResult result, {
54 | Future Function() refetch,
55 | FetchMore fetchMore,
56 | }) {
57 | Widget body;
58 | String name = '';
59 |
60 | if (result.hasException) {
61 | body = AlertBox(
62 | type: AlertType.error,
63 | text: result.exception.toString(),
64 | onRetry: () => refetch(),
65 | );
66 | } else if (result.loading) {
67 | body = const Center(
68 | child: CircularProgressIndicator(),
69 | );
70 | } else {
71 | final business = Business.fromJson(result.data['Business'][0]);
72 | name = business.name;
73 |
74 | body = SingleChildScrollView(
75 | child: Column(
76 | crossAxisAlignment: CrossAxisAlignment.stretch,
77 | children: [
78 | BusinessCard(business: business),
79 | Container(
80 | alignment: Alignment.center,
81 | padding: EdgeInsets.symmetric(
82 | horizontal: 16.0,
83 | vertical: 8.0,
84 | ),
85 | child: Text(
86 | 'Reviews',
87 | style: Theme.of(context).textTheme.headline6,
88 | ),
89 | ),
90 | ...ListTile.divideTiles(
91 | context: context,
92 | tiles: business.reviews
93 | .map((review) => ReviewTile(review: review))
94 | .toList(),
95 | ),
96 | ],
97 | ),
98 | );
99 | }
100 | return Scaffold(
101 | appBar: AppBar(
102 | title: Text(name),
103 | ),
104 | body: body,
105 | );
106 | },
107 | );
108 | }
109 | }
110 |
111 | class BusinessCard extends StatelessWidget {
112 | const BusinessCard({
113 | Key key,
114 | @required this.business,
115 | }) : super(key: key);
116 |
117 | final Business business;
118 |
119 | @override
120 | Widget build(BuildContext context) {
121 | return Padding(
122 | padding: const EdgeInsets.all(16.0),
123 | child: Column(
124 | crossAxisAlignment: CrossAxisAlignment.center,
125 | children: [
126 | Align(
127 | alignment: Alignment.center,
128 | child: RatingDisplay(
129 | rating: business.avgStars,
130 | size: 32.0,
131 | ),
132 | ),
133 | SizedBox(height: 12.0),
134 | Text(
135 | business.address,
136 | style: TextStyle(fontSize: 16.0),
137 | ),
138 | Text(
139 | '${business.city}, ${business.state}',
140 | style: TextStyle(fontSize: 18.0),
141 | ),
142 | SizedBox(height: 12.0),
143 | Wrap(
144 | spacing: 8.0,
145 | children: business.categories
146 | .map((cat) => Chip(label: Text(cat.name)))
147 | .toList(),
148 | ),
149 | ],
150 | ),
151 | );
152 | }
153 | }
154 |
155 | class ReviewTile extends StatelessWidget {
156 | final Review review;
157 |
158 | const ReviewTile({
159 | Key key,
160 | @required this.review,
161 | }) : super(key: key);
162 |
163 | @override
164 | Widget build(BuildContext context) {
165 | return Material(
166 | child: InkWell(
167 | onTap: () {
168 | final userId = review.user.userId;
169 | Navigator.push(
170 | context,
171 | MaterialPageRoute(
172 | builder: (context) => UserDetailScreen(userId: userId),
173 | ),
174 | );
175 | },
176 | child: Padding(
177 | padding: const EdgeInsets.symmetric(
178 | horizontal: 16.0,
179 | vertical: 12.0,
180 | ),
181 | child: Column(
182 | crossAxisAlignment: CrossAxisAlignment.start,
183 | children: [
184 | Row(
185 | mainAxisAlignment: MainAxisAlignment.spaceBetween,
186 | children: [
187 | RatingDisplay(
188 | rating: review.stars,
189 | size: 18.0,
190 | ),
191 | Text(
192 | '${review.date.month}/${review.date.day}/${review.date.year}',
193 | style: TextStyle(color: Colors.black54),
194 | ),
195 | ],
196 | ),
197 | SizedBox(height: 4.0),
198 | review.text != ''
199 | ? Align(
200 | alignment: Alignment.centerLeft,
201 | child: Text(
202 | review.text,
203 | style: TextStyle(color: Colors.black87),
204 | ),
205 | )
206 | : SizedBox.shrink(),
207 | review.text != '' ? SizedBox(height: 8.0) : SizedBox.shrink(),
208 | Text(
209 | '- ${review.user.name}',
210 | style: TextStyle(fontWeight: FontWeight.w500),
211 | ),
212 | ],
213 | ),
214 | ),
215 | ),
216 | );
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/web-react/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { Switch, Route, BrowserRouter as Router } from 'react-router-dom'
4 |
5 | import UserList from './components/UserList'
6 |
7 | import clsx from 'clsx'
8 | import { makeStyles } from '@material-ui/core/styles'
9 | import {
10 | CssBaseline,
11 | Drawer,
12 | Box,
13 | AppBar,
14 | Toolbar,
15 | List,
16 | Typography,
17 | Divider,
18 | IconButton,
19 | Container,
20 | Link as MUILink,
21 | ListItem,
22 | ListItemText,
23 | ListItemIcon,
24 | } from '@material-ui/core'
25 | import { Link } from 'react-router-dom'
26 | import {
27 | ChevronLeft as ChevronLeftIcon,
28 | Menu as MenuIcon,
29 | Dashboard as DashboardIcon,
30 | People as PeopleIcon,
31 | } from '@material-ui/icons'
32 | import Dashboard from './components/Dashboard'
33 |
34 | function Copyright() {
35 | return (
36 |
37 | {'Copyright © '}
38 |
39 | Your GRANDstack App Name Here
40 | {' '}
41 | {new Date().getFullYear()}
42 | {'.'}
43 |
44 | )
45 | }
46 |
47 | const drawerWidth = 240
48 |
49 | const useStyles = makeStyles((theme) => ({
50 | root: {
51 | display: 'flex',
52 | },
53 | toolbar: {
54 | paddingRight: 24, // keep right padding when drawer closed
55 | },
56 | toolbarIcon: {
57 | display: 'flex',
58 | alignItems: 'center',
59 | justifyContent: 'flex-end',
60 | padding: '0 8px',
61 | ...theme.mixins.toolbar,
62 | },
63 | appBar: {
64 | zIndex: theme.zIndex.drawer + 1,
65 | transition: theme.transitions.create(['width', 'margin'], {
66 | easing: theme.transitions.easing.sharp,
67 | duration: theme.transitions.duration.leavingScreen,
68 | }),
69 | },
70 | appBarShift: {
71 | marginLeft: drawerWidth,
72 | width: `calc(100% - ${drawerWidth}px)`,
73 | transition: theme.transitions.create(['width', 'margin'], {
74 | easing: theme.transitions.easing.sharp,
75 | duration: theme.transitions.duration.enteringScreen,
76 | }),
77 | },
78 | menuButton: {
79 | marginRight: 36,
80 | },
81 | menuButtonHidden: {
82 | display: 'none',
83 | },
84 | title: {
85 | flexGrow: 1,
86 | },
87 | drawerPaper: {
88 | position: 'relative',
89 | whiteSpace: 'nowrap',
90 | width: drawerWidth,
91 | transition: theme.transitions.create('width', {
92 | easing: theme.transitions.easing.sharp,
93 | duration: theme.transitions.duration.enteringScreen,
94 | }),
95 | },
96 | drawerPaperClose: {
97 | overflowX: 'hidden',
98 | transition: theme.transitions.create('width', {
99 | easing: theme.transitions.easing.sharp,
100 | duration: theme.transitions.duration.leavingScreen,
101 | }),
102 | width: theme.spacing(7),
103 | [theme.breakpoints.up('sm')]: {
104 | width: theme.spacing(9),
105 | },
106 | },
107 | appBarSpacer: theme.mixins.toolbar,
108 | content: {
109 | flexGrow: 1,
110 | height: '100vh',
111 | overflow: 'auto',
112 | },
113 | container: {
114 | paddingTop: theme.spacing(4),
115 | paddingBottom: theme.spacing(4),
116 | },
117 | paper: {
118 | padding: theme.spacing(2),
119 | display: 'flex',
120 | overflow: 'auto',
121 | flexDirection: 'column',
122 | },
123 | fixedHeight: {
124 | height: 240,
125 | },
126 | navLink: {
127 | textDecoration: 'none',
128 | color: 'inherit',
129 | },
130 | appBarImage: {
131 | maxHeight: '75px',
132 | paddingRight: '20px',
133 | },
134 | }))
135 |
136 | export default function App() {
137 | const classes = useStyles()
138 | const [open, setOpen] = React.useState(true)
139 | const handleDrawerOpen = () => {
140 | setOpen(true)
141 | }
142 | const handleDrawerClose = () => {
143 | setOpen(false)
144 | }
145 |
146 | return (
147 |
148 |
149 |
150 |
154 |
155 |
165 |
166 |
167 |
172 |
179 | Welcome To GRANDstack App
180 |
181 |
182 |
183 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 | )
234 | }
235 |
--------------------------------------------------------------------------------