├── 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 |
2 |
3 | 9 |
10 | Users 11 | Businesses 12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 |
-------------------------------------------------------------------------------- /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 | ![](../img/grandstack-flutter.png) 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 | 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 | 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 | ![GraphQL Playground](img/graphql-playground.png) 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | GRANDstack logo 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 | --------------------------------------------------------------------------------