├── .editorconfig ├── .gitignore ├── .graphqlconfig ├── .travis.yml ├── LICENSE ├── README.adoc ├── build-doc.sh ├── client ├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── pom.xml ├── postcss.config.js ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── AlbumCard.vue │ │ ├── CartNav.vue │ │ ├── GenreCard.vue │ │ ├── ReviewCard.vue │ │ ├── Stars.vue │ │ └── TrackList.vue │ ├── main.js │ ├── main │ │ └── resources │ │ │ └── webroot │ │ │ ├── css │ │ │ ├── app.4d7df893.css │ │ │ └── chunk-vendors.a1839bfa.css │ │ │ ├── favicon.ico │ │ │ ├── index.html │ │ │ └── js │ │ │ ├── app.f5bb45ac.js │ │ │ ├── app.f5bb45ac.js.map │ │ │ ├── chunk-vendors.274350e6.js │ │ │ └── chunk-vendors.274350e6.js.map │ ├── router │ │ └── index.js │ ├── shared │ │ └── apollo-client.js │ └── views │ │ ├── Album.vue │ │ ├── Cart.vue │ │ ├── Genre.vue │ │ └── Home.vue └── vue.config.js ├── common ├── pom.xml └── src │ └── main │ ├── java │ └── workshop │ │ ├── gateway │ │ ├── EnvironmentUtil.java │ │ ├── NotLoggedInException.java │ │ ├── RxDataFetcher.java │ │ └── WorkshopVerticle.java │ │ ├── model │ │ ├── Album.java │ │ ├── Cart.java │ │ ├── CartItem.java │ │ ├── Genre.java │ │ ├── RatingInfo.java │ │ ├── Review.java │ │ ├── ReviewInput.java │ │ └── Track.java │ │ └── repository │ │ ├── AlbumsRepository.java │ │ ├── CartRepository.java │ │ ├── GenresRepository.java │ │ ├── ListOfCodec.java │ │ ├── RatingRepository.java │ │ └── TracksRepository.java │ └── resources │ ├── vertx-default-jul-logging.properties │ └── webroot │ ├── images │ ├── albums │ │ ├── achtung-baby.png │ │ ├── appetite-for-destruction.png │ │ ├── automatic-for-the-people.png │ │ ├── back-in-black.png │ │ ├── dark-side-of-the-moon.png │ │ ├── enter-the-wu-tang.png │ │ ├── illmatic.png │ │ ├── master-of-puppets.png │ │ ├── ready-to-die.png │ │ ├── revolver.png │ │ ├── the-marshall-mathers-lp.png │ │ └── thriller.png │ └── genres │ │ ├── hip-hop.png │ │ ├── pop.png │ │ └── rock.png │ └── login.html ├── deploy-doc.sh ├── gateway ├── pom.xml ├── run.bat ├── run.sh └── src │ └── main │ ├── java │ └── workshop │ │ └── gateway │ │ ├── AddReviewDataFetcher.java │ │ ├── AddToCartDataFetcher.java │ │ ├── AlbumDataFetcher.java │ │ ├── AlbumRatingDataFetcher.java │ │ ├── AlbumReviewsDataFetcher.java │ │ ├── AlbumTracksDataFetcher.java │ │ ├── AlbumsDataFetcher.java │ │ ├── CartDataFetcher.java │ │ ├── CartItemAlbumDataFetcher.java │ │ ├── CurrentUserDataFetcher.java │ │ ├── GatewayServer.java │ │ ├── GenresDataFetcher.java │ │ ├── RemoveFromCartDataFetcher.java │ │ └── UserUtil.java │ └── resources │ ├── musicstore.graphql │ └── passwordfile ├── inventory ├── pom.xml ├── run.bat ├── run.sh └── src │ └── main │ ├── java │ └── workshop │ │ └── inventory │ │ ├── DataRepository.java │ │ └── InventoryServer.java │ └── resources │ ├── data │ ├── albums.csv │ ├── genres.csv │ └── tracks.csv │ └── vertx-default-jul-logging.properties ├── pom.xml ├── rating ├── pom.xml ├── run.bat ├── run.sh └── src │ └── main │ ├── java │ └── workshop │ │ └── rating │ │ └── RatingServer.java │ └── resources │ └── vertx-default-jul-logging.properties ├── run-postgres.sh ├── steps ├── pom.xml ├── step-0 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ └── java │ │ │ └── workshop │ │ │ └── gateway │ │ │ └── Step0Server.java │ │ └── solution │ │ └── java │ │ └── workshop │ │ └── gateway │ │ └── Step0Server.java ├── step-1 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ ├── java │ │ │ └── workshop │ │ │ │ └── gateway │ │ │ │ ├── GenresDataFetcher.java │ │ │ │ └── Step1Server.java │ │ └── resources │ │ │ └── musicstore.graphql │ │ └── solution │ │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── GenresDataFetcher.java │ │ │ └── Step1Server.java │ │ └── resources │ │ └── musicstore.graphql ├── step-2 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ ├── java │ │ │ └── workshop │ │ │ │ └── gateway │ │ │ │ ├── AlbumsDataFetcher.java │ │ │ │ ├── GenresDataFetcher.java │ │ │ │ └── Step2Server.java │ │ └── resources │ │ │ └── musicstore.graphql │ │ └── solution │ │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── AlbumsDataFetcher.java │ │ │ ├── GenresDataFetcher.java │ │ │ └── Step2Server.java │ │ └── resources │ │ └── musicstore.graphql ├── step-3 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ ├── java │ │ │ └── workshop │ │ │ │ └── gateway │ │ │ │ ├── AlbumDataFetcher.java │ │ │ │ ├── AlbumsDataFetcher.java │ │ │ │ ├── GenresDataFetcher.java │ │ │ │ └── Step3Server.java │ │ └── resources │ │ │ └── musicstore.graphql │ │ └── solution │ │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── AlbumDataFetcher.java │ │ │ ├── AlbumsDataFetcher.java │ │ │ ├── GenresDataFetcher.java │ │ │ └── Step3Server.java │ │ └── resources │ │ └── musicstore.graphql ├── step-4 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ ├── java │ │ │ └── workshop │ │ │ │ └── gateway │ │ │ │ ├── AlbumDataFetcher.java │ │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ │ ├── AlbumsDataFetcher.java │ │ │ │ ├── GenresDataFetcher.java │ │ │ │ └── Step4Server.java │ │ └── resources │ │ │ └── musicstore.graphql │ │ └── solution │ │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── AlbumDataFetcher.java │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ ├── AlbumsDataFetcher.java │ │ │ ├── GenresDataFetcher.java │ │ │ └── Step4Server.java │ │ └── resources │ │ └── musicstore.graphql ├── step-5 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ ├── java │ │ │ └── workshop │ │ │ │ └── gateway │ │ │ │ ├── AlbumDataFetcher.java │ │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ │ ├── AlbumsDataFetcher.java │ │ │ │ ├── CurrentUserDataFetcher.java │ │ │ │ ├── GenresDataFetcher.java │ │ │ │ ├── Step5Server.java │ │ │ │ └── UserUtil.java │ │ └── resources │ │ │ ├── musicstore.graphql │ │ │ └── passwordfile │ │ └── solution │ │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── AlbumDataFetcher.java │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ ├── AlbumsDataFetcher.java │ │ │ ├── CurrentUserDataFetcher.java │ │ │ ├── GenresDataFetcher.java │ │ │ ├── Step5Server.java │ │ │ └── UserUtil.java │ │ └── resources │ │ ├── musicstore.graphql │ │ └── passwordfile ├── step-6 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ │ ├── main │ │ ├── java │ │ │ └── workshop │ │ │ │ └── gateway │ │ │ │ ├── AddReviewDataFetcher.java │ │ │ │ ├── AlbumDataFetcher.java │ │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ │ ├── AlbumsDataFetcher.java │ │ │ │ ├── CurrentUserDataFetcher.java │ │ │ │ ├── GenresDataFetcher.java │ │ │ │ ├── Step6Server.java │ │ │ │ └── UserUtil.java │ │ └── resources │ │ │ ├── musicstore.graphql │ │ │ └── passwordfile │ │ └── solution │ │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── AddReviewDataFetcher.java │ │ │ ├── AlbumDataFetcher.java │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ ├── AlbumsDataFetcher.java │ │ │ ├── CurrentUserDataFetcher.java │ │ │ ├── GenresDataFetcher.java │ │ │ ├── Step6Server.java │ │ │ └── UserUtil.java │ │ └── resources │ │ ├── musicstore.graphql │ │ └── passwordfile └── step-7 │ ├── pom.xml │ ├── run-solution.bat │ ├── run-solution.sh │ ├── run.bat │ ├── run.sh │ └── src │ ├── main │ ├── java │ │ └── workshop │ │ │ └── gateway │ │ │ ├── AddReviewDataFetcher.java │ │ │ ├── AddToCartDataFetcher.java │ │ │ ├── AlbumDataFetcher.java │ │ │ ├── AlbumRatingDataFetcher.java │ │ │ ├── AlbumReviewsDataFetcher.java │ │ │ ├── AlbumTracksDataFetcher.java │ │ │ ├── AlbumsDataFetcher.java │ │ │ ├── CartDataFetcher.java │ │ │ ├── CartItemAlbumDataFetcher.java │ │ │ ├── CurrentUserDataFetcher.java │ │ │ ├── GenresDataFetcher.java │ │ │ ├── RemoveFromCartDataFetcher.java │ │ │ ├── Step7Server.java │ │ │ └── UserUtil.java │ └── resources │ │ ├── musicstore.graphql │ │ └── passwordfile │ └── solution │ ├── java │ └── workshop │ │ └── gateway │ │ ├── AddReviewDataFetcher.java │ │ ├── AddToCartDataFetcher.java │ │ ├── AlbumDataFetcher.java │ │ ├── AlbumRatingDataFetcher.java │ │ ├── AlbumReviewsDataFetcher.java │ │ ├── AlbumTracksDataFetcher.java │ │ ├── AlbumsDataFetcher.java │ │ ├── CartDataFetcher.java │ │ ├── CartItemAlbumDataFetcher.java │ │ ├── CurrentUserDataFetcher.java │ │ ├── GenresDataFetcher.java │ │ ├── RemoveFromCartDataFetcher.java │ │ ├── Step7Server.java │ │ └── UserUtil.java │ └── resources │ ├── musicstore.graphql │ └── passwordfile ├── watch-doc.sh └── workshop.adoc /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | charset = utf-8 12 | indent_style = space 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.graphqlconfig: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Music Store GraphQL Schema", 3 | "schemaPath": "gateway/src/main/resources/musicstore.graphql", 4 | "extensions": { 5 | "endpoints": { 6 | "Default GraphQL Endpoint": { 7 | "url": "http://localhost:8080/graphql" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | services: 3 | - docker 4 | branches: 5 | only: 6 | - master 7 | cache: 8 | directories: 9 | - $HOME/.m2 10 | env: 11 | global: 12 | - INPUT_FILE=workshop.adoc 13 | - OUTPUT_FILE=index.html 14 | - DEPLOY_LOCAL_DIR=gh-pages 15 | install: true 16 | jobs: 17 | include: 18 | - stage: build 19 | name: "Build - OpenJDK 8" 20 | script: 21 | - mvn -B clean verify 22 | - mvn -B clean verify -Psolution 23 | jdk: openjdk8 24 | - stage: build 25 | name: "Build - OpenJDK 11" 26 | script: 27 | - mvn -B clean verify 28 | - mvn -B clean verify -Psolution 29 | jdk: openjdk11 30 | - stage: deploy 31 | name: "Deploy doc" 32 | if: type != pull_request AND branch = master 33 | script: 34 | - mkdir ${DEPLOY_LOCAL_DIR} 35 | - docker run -u $(id -u):$(id -g) -v ${PWD}:/documents/ -v ${PWD}/${DEPLOY_LOCAL_DIR}:/output/ asciidoctor/docker-asciidoctor asciidoctor -r asciidoctor-diagram /documents/${INPUT_FILE} -D /output -o ${OUTPUT_FILE} 36 | - rm -rf ${PWD}/${DEPLOY_LOCAL_DIR}/.asciidoctor 37 | deploy: 38 | provider: pages 39 | github_token: ${GITHUB_TOKEN} 40 | skip_cleanup: true 41 | local_dir: ${DEPLOY_LOCAL_DIR} 42 | keep_history: true 43 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Implementing the API Gateway pattern with GraphQL 2 | 3 | image:https://travis-ci.org/tsegismont/graphql-api-gateway-workshop.svg?branch=master["Build Status", link="https://travis-ci.org/tsegismont/graphql-api-gateway-workshop"] 4 | 5 | Take the workshop online: https://tsegismont.github.io/graphql-api-gateway-workshop/ 6 | -------------------------------------------------------------------------------- /build-doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | INPUT_FILE=workshop.adoc 6 | OUTPUT_DIR="${PWD}/target/workshop-doc" 7 | OUTPUT_FILE=index.html 8 | 9 | mkdir -p "${OUTPUT_DIR}" 10 | docker run -u $(id -u):$(id -g) -v "${PWD}":/documents/ -v "${OUTPUT_DIR}":/output/ asciidoctor/docker-asciidoctor asciidoctor -r asciidoctor-diagram /documents/${INPUT_FILE} -D /output -o ${OUTPUT_FILE} 11 | -------------------------------------------------------------------------------- /client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | }; 6 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build && mkdir -p src/main/resources/webroot/ && cp -TR dist/ src/main/resources/webroot/", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-svg-core": "1.2.25", 12 | "@fortawesome/free-regular-svg-icons": "5.11.2", 13 | "@fortawesome/free-solid-svg-icons": "5.11.2", 14 | "@fortawesome/vue-fontawesome": "0.1.7", 15 | "apollo-cache-inmemory": "1.6.3", 16 | "apollo-client": "2.6.4", 17 | "apollo-link-http": "1.5.16", 18 | "bootstrap": "^4.3.1", 19 | "core-js": "^3.1.2", 20 | "graphql": "^14.5.8", 21 | "graphql-tag": "2.10.1", 22 | "jquery": "^3.5.1", 23 | "popper.js": "^1.16.0", 24 | "vue": "^3.0.0", 25 | "vue-router": "^3.0.6" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^5.0.8", 29 | "@vue/cli-plugin-eslint": "^5.0.8", 30 | "@vue/cli-plugin-router": "^5.0.8", 31 | "@vue/cli-service": "^5.0.8", 32 | "babel-eslint": "^10.0.1", 33 | "eslint": "^5.16.0", 34 | "eslint-plugin-vue": "^5.0.0", 35 | "vue-template-compiler": "^2.6.10" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | client 14 | 15 | 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-compiler-plugin 20 | 21 | true 22 | 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-surefire-plugin 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Music Store 9 | 10 | 11 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/components/AlbumCard.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /client/src/components/CartNav.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /client/src/components/GenreCard.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /client/src/components/ReviewCard.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | -------------------------------------------------------------------------------- /client/src/components/Stars.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /client/src/components/TrackList.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 29 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap' 2 | import 'bootstrap/dist/css/bootstrap.min.css' 3 | import Vue from 'vue' 4 | import App from './App.vue' 5 | import router from './router' 6 | 7 | import {library} from '@fortawesome/fontawesome-svg-core' 8 | import {faStar as farStar} from '@fortawesome/free-regular-svg-icons' 9 | import {faShoppingCart, faStar as fasStar, faUserCircle} from '@fortawesome/free-solid-svg-icons' 10 | import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome' 11 | 12 | library.add(faUserCircle, farStar, fasStar, faShoppingCart); 13 | 14 | Vue.component('font-awesome-icon', FontAwesomeIcon); 15 | 16 | Vue.config.productionTip = false; 17 | 18 | new Vue({ 19 | router, 20 | render: h => h(App) 21 | }).$mount('#app'); 22 | -------------------------------------------------------------------------------- /client/src/main/resources/webroot/css/app.4d7df893.css: -------------------------------------------------------------------------------- 1 | nav{margin-bottom:2em}div.row{margin-top:2em}div.card{margin:0 2em 2em 0}img.cart-album[data-v-b2f30ea6]{max-width:100px;height:auto} -------------------------------------------------------------------------------- /client/src/main/resources/webroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/client/src/main/resources/webroot/favicon.ico -------------------------------------------------------------------------------- /client/src/main/resources/webroot/index.html: -------------------------------------------------------------------------------- 1 | Music Store
-------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | import Genre from '../views/Genre.vue' 5 | import Album from '../views/Album.vue' 6 | import Cart from "../views/Cart"; 7 | 8 | Vue.use(VueRouter); 9 | 10 | const routes = [ 11 | { 12 | path: '/', 13 | name: 'home', 14 | component: Home 15 | }, 16 | { 17 | path: '/genre/:id', 18 | name: 'genre', 19 | component: Genre, 20 | }, 21 | { 22 | path: '/album/:id', 23 | name: 'album', 24 | component: Album, 25 | }, { 26 | path: '/cart', 27 | name: 'cart', 28 | component: Cart, 29 | } 30 | ]; 31 | 32 | const router = new VueRouter({ 33 | mode: 'history', 34 | routes: routes 35 | }); 36 | 37 | export default router; 38 | -------------------------------------------------------------------------------- /client/src/shared/apollo-client.js: -------------------------------------------------------------------------------- 1 | import {ApolloClient} from 'apollo-client'; 2 | import {createHttpLink} from 'apollo-link-http'; 3 | import {InMemoryCache} from 'apollo-cache-inmemory'; 4 | 5 | const defaultOptions = { 6 | query: { 7 | fetchPolicy: 'no-cache', 8 | errorPolicy: 'all', 9 | }, 10 | }; 11 | 12 | const link = createHttpLink({ 13 | uri: '/graphql', 14 | credentials: 'same-origin' 15 | }); 16 | 17 | const apolloClient = new ApolloClient({ 18 | cache: new InMemoryCache(), 19 | link, 20 | defaultOptions, 21 | }); 22 | 23 | export default apolloClient; 24 | -------------------------------------------------------------------------------- /client/src/views/Genre.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 75 | -------------------------------------------------------------------------------- /client/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 72 | -------------------------------------------------------------------------------- /client/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 3000, 4 | proxy: { 5 | '^/login.html$': { 6 | target: 'http://localhost:8080', 7 | }, 8 | '^/logout$': { 9 | target: 'http://localhost:8080', 10 | }, 11 | '^/images/*': { 12 | target: 'http://localhost:8080', 13 | }, 14 | '^/graphql$': { 15 | target: 'http://localhost:8080' 16 | 17 | } 18 | } 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | common 14 | 15 | 16 | 17 | 18 | io.vertx 19 | vertx-stack-depchain 20 | ${vertx.version} 21 | pom 22 | import 23 | 24 | 25 | 26 | 27 | 28 | 29 | io.vertx 30 | vertx-rx-java2 31 | 32 | 33 | com.github.akarnokd 34 | rxjava2-jdk8-interop 35 | 36 | 37 | io.vertx 38 | vertx-web-graphql 39 | 40 | 41 | io.vertx 42 | vertx-web-client 43 | 44 | 45 | io.vertx 46 | vertx-pg-client 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/gateway/EnvironmentUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | import java.util.Map; 7 | 8 | public class EnvironmentUtil { 9 | 10 | public static T getInputArgument(DataFetchingEnvironment env, String name, Class inputType) { 11 | Map argument = env.getArgument(name); 12 | return new JsonObject(argument).mapTo(inputType); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/gateway/NotLoggedInException.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | public class NotLoggedInException extends Exception { 4 | 5 | public NotLoggedInException() { 6 | super("Not logged in", null, false, false); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/gateway/RxDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | import hu.akarnokd.rxjava2.interop.SingleInterop; 6 | import io.reactivex.Single; 7 | 8 | import java.util.concurrent.CompletionStage; 9 | 10 | public interface RxDataFetcher extends DataFetcher> { 11 | 12 | @Override 13 | default CompletionStage get(DataFetchingEnvironment environment) throws Exception { 14 | Single single = rxGet(environment); 15 | return single==null ? null:single.to(SingleInterop.get()); 16 | } 17 | 18 | Single rxGet(DataFetchingEnvironment env) throws Exception; 19 | } 20 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/Album.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | import java.util.List; 4 | 5 | public class Album { 6 | 7 | private Integer id; 8 | private String name; 9 | private Genre genre; 10 | private String artist; 11 | private String image; 12 | private List tracks; 13 | private Integer rating; 14 | private List reviews; 15 | 16 | public Integer getId() { 17 | return id; 18 | } 19 | 20 | public void setId(Integer id) { 21 | this.id = id; 22 | } 23 | 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | public void setName(String name) { 29 | this.name = name; 30 | } 31 | 32 | public Genre getGenre() { 33 | return genre; 34 | } 35 | 36 | public void setGenre(Genre genre) { 37 | this.genre = genre; 38 | } 39 | 40 | public String getArtist() { 41 | return artist; 42 | } 43 | 44 | public void setArtist(String artist) { 45 | this.artist = artist; 46 | } 47 | 48 | public String getImage() { 49 | return image; 50 | } 51 | 52 | public void setImage(String image) { 53 | this.image = image; 54 | } 55 | 56 | public List getTracks() { 57 | return tracks; 58 | } 59 | 60 | public void setTracks(List tracks) { 61 | this.tracks = tracks; 62 | } 63 | 64 | public Integer getRating() { 65 | return rating; 66 | } 67 | 68 | public void setRating(Integer rating) { 69 | this.rating = rating; 70 | } 71 | 72 | public List getReviews() { 73 | return reviews; 74 | } 75 | 76 | public void setReviews(List reviews) { 77 | this.reviews = reviews; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return "Album{" + 83 | "id=" + id + 84 | ", name='" + name + '\'' + 85 | ", genre=" + genre + 86 | ", artist='" + artist + '\'' + 87 | ", image='" + image + '\'' + 88 | ", tracks=" + tracks + 89 | ", rating=" + rating + 90 | ", reviews=" + reviews + 91 | '}'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/Cart.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Cart { 7 | 8 | private List items; 9 | 10 | public List getItems() { 11 | return items; 12 | } 13 | 14 | public void setItems(List items) { 15 | this.items = items; 16 | } 17 | 18 | public void add(CartItem cartItem) { 19 | if (items==null) { 20 | items = new ArrayList<>(); 21 | } 22 | items.add(cartItem); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "Cart{" + 28 | "items=" + items + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/CartItem.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | public class CartItem { 4 | 5 | private Integer albumId; 6 | private Integer quantity; 7 | 8 | public CartItem() { 9 | } 10 | 11 | public CartItem(Integer albumId, Integer quantity) { 12 | this.albumId = albumId; 13 | this.quantity = quantity; 14 | } 15 | 16 | public Integer getAlbumId() { 17 | return albumId; 18 | } 19 | 20 | public void setAlbumId(Integer albumId) { 21 | this.albumId = albumId; 22 | } 23 | 24 | public Integer getQuantity() { 25 | return quantity; 26 | } 27 | 28 | public void setQuantity(Integer quantity) { 29 | this.quantity = quantity; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "CartItem{" + 35 | "albumId=" + albumId + 36 | ", quantity=" + quantity + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/Genre.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | public class Genre { 4 | 5 | private Integer id; 6 | private String name; 7 | private String image; 8 | 9 | public Integer getId() { 10 | return id; 11 | } 12 | 13 | public void setId(Integer id) { 14 | this.id = id; 15 | } 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public String getImage() { 26 | return image; 27 | } 28 | 29 | public void setImage(String image) { 30 | this.image = image; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Genre{" + 36 | "id=" + id + 37 | ", name='" + name + '\'' + 38 | ", image='" + image + '\'' + 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/RatingInfo.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | import java.util.List; 4 | 5 | public class RatingInfo { 6 | 7 | private Integer rating; 8 | private List reviews; 9 | 10 | public Integer getRating() { 11 | return rating; 12 | } 13 | 14 | public void setRating(Integer rating) { 15 | this.rating = rating; 16 | } 17 | 18 | public List getReviews() { 19 | return reviews; 20 | } 21 | 22 | public void setReviews(List reviews) { 23 | this.reviews = reviews; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "ReviewResult{" + 29 | "rating=" + rating + 30 | ", reviews=" + reviews + 31 | '}'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/Review.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | public class Review { 4 | 5 | private String name; 6 | private Integer rating; 7 | private String comment; 8 | 9 | public String getName() { 10 | return name; 11 | } 12 | 13 | public void setName(String name) { 14 | this.name = name; 15 | } 16 | 17 | public Integer getRating() { 18 | return rating; 19 | } 20 | 21 | public void setRating(Integer rating) { 22 | this.rating = rating; 23 | } 24 | 25 | public String getComment() { 26 | return comment; 27 | } 28 | 29 | public void setComment(String comment) { 30 | this.comment = comment; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Review{" + 36 | "name='" + name + '\'' + 37 | ", rating=" + rating + 38 | ", comment='" + comment + '\'' + 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/ReviewInput.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | public class ReviewInput { 4 | 5 | private Integer rating; 6 | private String comment; 7 | private String name; 8 | 9 | public Integer getRating() { 10 | return rating; 11 | } 12 | 13 | public void setRating(Integer rating) { 14 | this.rating = rating; 15 | } 16 | 17 | public String getComment() { 18 | return comment; 19 | } 20 | 21 | public void setComment(String comment) { 22 | this.comment = comment; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public void setName(String name) { 30 | this.name = name; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "ReviewInput{" + 36 | "rating=" + rating + 37 | ", comment='" + comment + '\'' + 38 | ", name='" + name + '\'' + 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/model/Track.java: -------------------------------------------------------------------------------- 1 | package workshop.model; 2 | 3 | public class Track { 4 | 5 | private Integer number; 6 | private String name; 7 | 8 | public Integer getNumber() { 9 | return number; 10 | } 11 | 12 | public void setNumber(Integer number) { 13 | this.number = number; 14 | } 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "Track{" + 27 | "number=" + number + 28 | ", name='" + name + '\'' + 29 | '}'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/repository/AlbumsRepository.java: -------------------------------------------------------------------------------- 1 | package workshop.repository; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.ext.web.client.HttpRequest; 5 | import io.vertx.reactivex.ext.web.client.HttpResponse; 6 | import io.vertx.reactivex.ext.web.client.WebClient; 7 | import io.vertx.reactivex.ext.web.client.predicate.ResponsePredicate; 8 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 9 | import workshop.model.Album; 10 | 11 | import java.util.List; 12 | 13 | public class AlbumsRepository { 14 | 15 | private final WebClient inventoryClient; 16 | 17 | public AlbumsRepository(WebClient inventoryClient) { 18 | this.inventoryClient = inventoryClient; 19 | } 20 | 21 | public Single> findAll(Integer genre) { 22 | HttpRequest> request = inventoryClient.get("/albums") 23 | .expect(ResponsePredicate.SC_OK) 24 | .expect(ResponsePredicate.JSON) 25 | .as(ListOfCodec.create(Album.class)); 26 | if (genre != null) { 27 | request.addQueryParam("genre", genre.toString()); 28 | } 29 | return request 30 | .rxSend() 31 | .map(HttpResponse::body); 32 | } 33 | 34 | public Single findById(Integer id, boolean withTracks) { 35 | HttpRequest request = inventoryClient.get("/album/" + id) 36 | .expect(ResponsePredicate.SC_OK) 37 | .expect(ResponsePredicate.JSON) 38 | .as(BodyCodec.json(Album.class)); 39 | if (withTracks) { 40 | request.addQueryParam("withTracks", ""); 41 | } 42 | return request 43 | .rxSend() 44 | .map(HttpResponse::body); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/repository/CartRepository.java: -------------------------------------------------------------------------------- 1 | package workshop.repository; 2 | 3 | import io.reactivex.Completable; 4 | import io.reactivex.Observable; 5 | import io.reactivex.Single; 6 | import io.vertx.reactivex.pgclient.PgPool; 7 | import io.vertx.reactivex.sqlclient.Row; 8 | import io.vertx.reactivex.sqlclient.Tuple; 9 | import workshop.model.Cart; 10 | import workshop.model.CartItem; 11 | 12 | public class CartRepository { 13 | 14 | private static final String FIND_CART = "select album_id, quantity from cart where username = $1 and quantity > 0"; 15 | 16 | private static final String ADD_TO_CART = "insert into cart (username, album_id, quantity)" 17 | + " " 18 | + "values ($1, $2, 1)" 19 | + " " 20 | + "on conflict (username, album_id) do update set quantity = cart.quantity + 1"; 21 | 22 | private static final String REMOVE_FROM_CART = "update cart set quantity = quantity - 1" 23 | + " " 24 | + "where username = $1 and album_id = $2 and quantity > 0"; 25 | 26 | private final PgPool pool; 27 | 28 | public CartRepository(PgPool pool) { 29 | this.pool = pool; 30 | } 31 | 32 | public Single findCart(String username) { 33 | return pool.rxPreparedQuery(FIND_CART, Tuple.of(username)) 34 | .flatMapObservable(Observable::fromIterable) 35 | .map(CartRepository::rowToCartItem) 36 | .collectInto(new Cart(), Cart::add); 37 | } 38 | 39 | public Completable addToCart(String username, Integer albumId) { 40 | return pool.rxPreparedQuery(ADD_TO_CART, Tuple.of(username, albumId)) 41 | .ignoreElement(); 42 | } 43 | 44 | public Completable removeFromCart(String username, Integer albumId) { 45 | return pool.rxPreparedQuery(REMOVE_FROM_CART, Tuple.of(username, albumId)) 46 | .ignoreElement(); 47 | } 48 | 49 | private static CartItem rowToCartItem(Row row) { 50 | return new CartItem(row.getInteger("album_id"), row.getInteger("quantity")); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/repository/GenresRepository.java: -------------------------------------------------------------------------------- 1 | package workshop.repository; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.client.WebClient; 6 | import io.vertx.reactivex.ext.web.client.predicate.ResponsePredicate; 7 | import workshop.model.Genre; 8 | 9 | import java.util.List; 10 | 11 | public class GenresRepository { 12 | 13 | private final WebClient inventoryClient; 14 | 15 | public GenresRepository(WebClient inventoryClient) { 16 | this.inventoryClient = inventoryClient; 17 | } 18 | 19 | public Single> findAll() { 20 | return inventoryClient.get("/genres") 21 | .expect(ResponsePredicate.SC_OK) 22 | .expect(ResponsePredicate.JSON) 23 | .as(ListOfCodec.create(Genre.class)) 24 | .rxSend() 25 | .map(HttpResponse::body); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/repository/ListOfCodec.java: -------------------------------------------------------------------------------- 1 | package workshop.repository; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.stream.Collectors.toList; 9 | 10 | public class ListOfCodec { 11 | 12 | public static BodyCodec> create(Class listType) { 13 | return BodyCodec.create(buffer -> { 14 | return buffer.toJsonArray() 15 | .stream() 16 | .map(JsonObject.class::cast) 17 | .map(json -> json.mapTo(listType)) 18 | .collect(toList()); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/repository/RatingRepository.java: -------------------------------------------------------------------------------- 1 | package workshop.repository; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.client.WebClient; 6 | import io.vertx.reactivex.ext.web.client.predicate.ResponsePredicate; 7 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 8 | import workshop.model.RatingInfo; 9 | import workshop.model.Review; 10 | import workshop.model.ReviewInput; 11 | 12 | import java.util.List; 13 | 14 | public class RatingRepository { 15 | 16 | private final WebClient ratingClient; 17 | 18 | public RatingRepository(WebClient ratingClient) { 19 | this.ratingClient = ratingClient; 20 | } 21 | 22 | public Single findRatingByAlbum(Integer albumId) { 23 | return ratingClient.get("/album/" + albumId + "/rating") 24 | .expect(ResponsePredicate.SC_OK) 25 | .expect(ResponsePredicate.JSON) 26 | .as(BodyCodec.jsonObject()) 27 | .rxSend() 28 | .map(HttpResponse::body) 29 | .map(json -> json.getInteger("value")); 30 | } 31 | 32 | public Single> findReviewsByAlbum(Integer albumId) { 33 | return ratingClient.get("/album/" + albumId + "/reviews") 34 | .expect(ResponsePredicate.SC_OK) 35 | .expect(ResponsePredicate.JSON) 36 | .as(ListOfCodec.create(Review.class)) 37 | .rxSend() 38 | .map(HttpResponse::body); 39 | } 40 | 41 | public Single findRatingAndReviewsByAlbum(Integer albumId) { 42 | return ratingClient.get("/album/" + albumId) 43 | .expect(ResponsePredicate.SC_OK) 44 | .expect(ResponsePredicate.JSON) 45 | .as(BodyCodec.json(RatingInfo.class)) 46 | .rxSend() 47 | .map(HttpResponse::body); 48 | } 49 | 50 | public Single addReview(Integer albumId, ReviewInput reviewInput) { 51 | return ratingClient.post("/album/" + albumId + "/reviews") 52 | .expect(ResponsePredicate.SC_OK) 53 | .expect(ResponsePredicate.JSON) 54 | .as(BodyCodec.json(RatingInfo.class)) 55 | .rxSendJson(reviewInput) 56 | .map(HttpResponse::body); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /common/src/main/java/workshop/repository/TracksRepository.java: -------------------------------------------------------------------------------- 1 | package workshop.repository; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.client.WebClient; 6 | import io.vertx.reactivex.ext.web.client.predicate.ResponsePredicate; 7 | import workshop.model.Track; 8 | 9 | import java.util.List; 10 | 11 | public class TracksRepository { 12 | 13 | private final WebClient inventoryClient; 14 | 15 | public TracksRepository(WebClient inventoryClient) { 16 | this.inventoryClient = inventoryClient; 17 | } 18 | 19 | public Single> findByAlbum(Integer id) { 20 | return inventoryClient.get("/album/" + id + "/tracks") 21 | .expect(ResponsePredicate.SC_OK) 22 | .expect(ResponsePredicate.JSON) 23 | .as(ListOfCodec.create(Track.class)) 24 | .rxSend() 25 | .map(HttpResponse::body); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /common/src/main/resources/vertx-default-jul-logging.properties: -------------------------------------------------------------------------------- 1 | handlers=java.util.logging.ConsoleHandler 2 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 3 | java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s%6$s%n 4 | java.util.logging.ConsoleHandler.level=INFO 5 | .level=INFO 6 | io.vertx.level=INFO 7 | io.netty.util.internal.PlatformDependent.level=SEVERE 8 | -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/achtung-baby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/achtung-baby.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/appetite-for-destruction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/appetite-for-destruction.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/automatic-for-the-people.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/automatic-for-the-people.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/back-in-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/back-in-black.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/dark-side-of-the-moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/dark-side-of-the-moon.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/enter-the-wu-tang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/enter-the-wu-tang.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/illmatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/illmatic.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/master-of-puppets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/master-of-puppets.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/ready-to-die.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/ready-to-die.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/revolver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/revolver.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/the-marshall-mathers-lp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/the-marshall-mathers-lp.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/albums/thriller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/albums/thriller.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/genres/hip-hop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/genres/hip-hop.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/genres/pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/genres/pop.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/images/genres/rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsegismont/graphql-api-gateway-workshop/2ace19297d69e21a8044e89acbcecbebf6e0ea23/common/src/main/resources/webroot/images/genres/rock.png -------------------------------------------------------------------------------- /common/src/main/resources/webroot/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Music Store 9 | 10 | 11 |

Fake external authentication system

12 |
13 |

14 |

15 |

16 | 17 | 18 |

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /deploy-doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | WORKDIR=$(mktemp -d) 6 | REMOTE_REPO=git@github.com:tsegismont/graphql-api-gateway-workshop.git 7 | WEBSITE_BRANCH=gh-pages 8 | LOCAL_MASTER_REPO="${WORKDIR}/master" 9 | LOCAL_PAGES_REPO="${WORKDIR}/${WEBSITE_BRANCH}" 10 | INPUT_FILE=workshop.adoc 11 | OUTPUT_FILE=index.html 12 | 13 | mkdir -p "${LOCAL_MASTER_REPO}" 14 | git clone ${REMOTE_REPO} "${LOCAL_MASTER_REPO}" 15 | mkdir -p "${LOCAL_PAGES_REPO}" 16 | git clone -b ${WEBSITE_BRANCH} ${REMOTE_REPO} "${LOCAL_PAGES_REPO}" 17 | docker run -u $(id -u):$(id -g) -v "${LOCAL_MASTER_REPO}":/documents/ -v "${LOCAL_PAGES_REPO}":/output/ asciidoctor/docker-asciidoctor asciidoctor -r asciidoctor-diagram /documents/${INPUT_FILE} -D /output -o ${OUTPUT_FILE} 18 | cd "${LOCAL_PAGES_REPO}" 19 | rm -rf .asciidoctor 20 | git add . 21 | git commit -m "Auto generated doc" 22 | git push origin ${WEBSITE_BRANCH} 23 | -------------------------------------------------------------------------------- /gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | gateway 14 | 15 | 16 | workshop.gateway.GatewayServer 17 | 18 | 19 | 20 | 21 | 22 | io.vertx 23 | vertx-stack-depchain 24 | ${vertx.version} 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | 33 | graphql-api-gateway-workshop 34 | common 35 | ${project.version} 36 | 37 | 38 | graphql-api-gateway-workshop 39 | client 40 | ${project.version} 41 | 42 | 43 | io.vertx 44 | vertx-rx-java2 45 | 46 | 47 | com.github.akarnokd 48 | rxjava2-jdk8-interop 49 | 50 | 51 | io.vertx 52 | vertx-web-graphql 53 | 54 | 55 | io.vertx 56 | vertx-auth-htpasswd 57 | 58 | 59 | io.vertx 60 | vertx-web-client 61 | 62 | 63 | io.vertx 64 | vertx-pg-client 65 | 66 | 67 | org.slf4j 68 | slf4j-jdk14 69 | 70 | 71 | 72 | 73 | ${project.artifactId} 74 | 75 | 76 | io.reactiverse 77 | vertx-maven-plugin 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /gateway/run.bat: -------------------------------------------------------------------------------- 1 | java -jar target\gateway.jar 2 | -------------------------------------------------------------------------------- /gateway/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | java -jar target/gateway.jar 6 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AddReviewDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.RatingInfo; 6 | import workshop.model.ReviewInput; 7 | import workshop.repository.RatingRepository; 8 | 9 | public class AddReviewDataFetcher implements RxDataFetcher { 10 | 11 | private final RatingRepository ratingRepository; 12 | 13 | public AddReviewDataFetcher(RatingRepository ratingRepository) { 14 | this.ratingRepository = ratingRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | String currentUser = UserUtil.currentUser(env); 20 | if (currentUser==null) { 21 | throw new NotLoggedInException(); 22 | } 23 | Integer albumId = env.getArgument("albumId"); 24 | ReviewInput reviewInput = EnvironmentUtil.getInputArgument(env, "review", ReviewInput.class); 25 | reviewInput.setName(currentUser); 26 | return ratingRepository.addReview(albumId, reviewInput); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AddToCartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class AddToCartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public AddToCartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | String currentUser = UserUtil.currentUser(env); 19 | if (currentUser==null) { 20 | throw new NotLoggedInException(); 21 | } 22 | Integer albumId = env.getArgument("albumId"); 23 | return cartRepository.addToCart(currentUser, albumId).andThen(cartRepository.findCart(currentUser)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/CartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class CartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public CartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) { 18 | String curentUser = UserUtil.currentUser(env); 19 | return curentUser==null ? null:cartRepository.findCart(curentUser); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/CartItemAlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.CartItem; 7 | import workshop.repository.AlbumsRepository; 8 | 9 | public class CartItemAlbumDataFetcher implements RxDataFetcher { 10 | 11 | private final AlbumsRepository albumsRepository; 12 | 13 | public CartItemAlbumDataFetcher(AlbumsRepository albumsRepository) { 14 | this.albumsRepository = albumsRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | CartItem cartItem = env.getSource(); 20 | return albumsRepository.findById(cartItem.getAlbumId(), false); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/RemoveFromCartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class RemoveFromCartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public RemoveFromCartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | String currentUser = UserUtil.currentUser(env); 19 | if (currentUser==null) { 20 | throw new NotLoggedInException(); 21 | } 22 | Integer albumId = env.getArgument("albumId"); 23 | return cartRepository.removeFromCart(currentUser, albumId).andThen(cartRepository.findCart(currentUser)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /gateway/src/main/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | RoutingContext routingContext = env.getContext(); 11 | User user = routingContext.user(); 12 | return user!=null ? user.principal().getString("username"):null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gateway/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type CartItem { 30 | album: Album 31 | quantity: Int 32 | } 33 | 34 | type Cart { 35 | items: [CartItem!] 36 | } 37 | 38 | type Query { 39 | genres: [Genre!] 40 | albums(genre: Int): [Album!] 41 | album(id: Int!): Album 42 | cart: Cart 43 | currentUser: String 44 | } 45 | 46 | input ReviewInput { 47 | rating: Int! 48 | comment: String 49 | } 50 | 51 | type ReviewResult { 52 | rating: Int! 53 | reviews: [Review!]! 54 | } 55 | 56 | type Mutation { 57 | addToCart(albumId: Int!): Cart 58 | removeFromCart(albumId: Int!): Cart 59 | addReview(albumId: Int!, review: ReviewInput): ReviewResult 60 | } 61 | 62 | schema { 63 | query: Query 64 | mutation: Mutation 65 | } 66 | -------------------------------------------------------------------------------- /gateway/src/main/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /inventory/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | inventory 14 | 15 | 16 | workshop.inventory.InventoryServer 17 | 18 | 19 | 20 | 21 | 22 | io.vertx 23 | vertx-stack-depchain 24 | ${vertx.version} 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | 33 | io.vertx 34 | vertx-web 35 | 36 | 37 | 38 | 39 | ${project.artifactId} 40 | 41 | 42 | io.reactiverse 43 | vertx-maven-plugin 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /inventory/run.bat: -------------------------------------------------------------------------------- 1 | java -jar target\inventory.jar 2 | -------------------------------------------------------------------------------- /inventory/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | java -jar target/inventory.jar 6 | -------------------------------------------------------------------------------- /inventory/src/main/java/workshop/inventory/DataRepository.java: -------------------------------------------------------------------------------- 1 | package workshop.inventory; 2 | 3 | import io.vertx.core.Vertx; 4 | import io.vertx.core.json.JsonArray; 5 | import io.vertx.core.json.JsonObject; 6 | 7 | import java.util.Arrays; 8 | import java.util.Map; 9 | import java.util.stream.Collector; 10 | 11 | import static java.util.stream.Collectors.groupingBy; 12 | 13 | public class DataRepository { 14 | 15 | public final JsonArray genres; 16 | public final JsonArray albums; 17 | public final Map tracksByAlbum; 18 | 19 | public DataRepository(Vertx vertx) { 20 | genres = loadGenreData(vertx); 21 | albums = loadAlbumData(vertx); 22 | tracksByAlbum = loadTrackData(vertx); 23 | } 24 | 25 | public JsonArray loadGenreData(Vertx vertx) { 26 | String content = vertx.fileSystem().readFileBlocking("data/genres.csv").toString(); 27 | String[] lines = content.split("\n"); 28 | return Arrays.stream(lines) 29 | .skip(1) 30 | .map(line -> line.split(",")) 31 | .map(row -> new JsonObject() 32 | .put("id", Integer.parseInt(row[0].trim())) 33 | .put("name", row[1].trim()) 34 | .put("image", row[2].trim()) 35 | ) 36 | .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); 37 | } 38 | 39 | public JsonArray loadAlbumData(Vertx vertx) { 40 | String content = vertx.fileSystem().readFileBlocking("data/albums.csv").toString(); 41 | String[] lines = content.split("\n"); 42 | return Arrays.stream(lines) 43 | .skip(1) 44 | .map(line -> line.split(",")) 45 | .map(row -> new JsonObject() 46 | .put("id", Integer.parseInt(row[0].trim())) 47 | .put("name", row[1].trim()) 48 | .put("genre", genres.stream() 49 | .map(JsonObject.class::cast) 50 | .filter(genre -> genre.getInteger("id").equals(Integer.parseInt(row[2].trim()))) 51 | .findFirst() 52 | .orElse(null) 53 | ) 54 | .put("artist", row[3].trim()) 55 | .put("image", row[4].trim()) 56 | ) 57 | .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); 58 | } 59 | 60 | public Map loadTrackData(Vertx vertx) { 61 | String content = vertx.fileSystem().readFileBlocking("data/tracks.csv").toString(); 62 | String[] lines = content.split("\n"); 63 | return Arrays.stream(lines) 64 | .skip(1) 65 | .map(line -> line.split(",")) 66 | .map(row -> new JsonObject() 67 | .put("album", Integer.parseInt(row[0].trim())) 68 | .put("number", Integer.parseInt(row[1].trim())) 69 | .put("name", row[2].trim()) 70 | ) 71 | .collect( 72 | groupingBy( 73 | json -> json.getInteger("album"), 74 | Collector.of( 75 | JsonArray::new, 76 | (array, value) -> array.add(new JsonObject().put("number", value.getInteger("number")).put("name", value.getString("name"))), 77 | JsonArray::addAll 78 | ) 79 | ) 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /inventory/src/main/java/workshop/inventory/InventoryServer.java: -------------------------------------------------------------------------------- 1 | package workshop.inventory; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.http.HttpHeaders; 5 | import io.vertx.core.json.JsonArray; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.ext.web.Router; 8 | import io.vertx.ext.web.RoutingContext; 9 | import io.vertx.ext.web.handler.ErrorHandler; 10 | import io.vertx.ext.web.handler.LoggerFormat; 11 | import io.vertx.ext.web.handler.LoggerHandler; 12 | 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | import static java.util.stream.Collectors.toList; 17 | 18 | public class InventoryServer extends AbstractVerticle { 19 | 20 | private DataRepository dataRepository; 21 | 22 | @Override 23 | public void start() throws Exception { 24 | 25 | dataRepository = new DataRepository(vertx); 26 | 27 | Router router = Router.router(vertx); 28 | 29 | router.route().handler(LoggerHandler.create(LoggerFormat.TINY)); 30 | 31 | router.route().handler(this::setResponseContentType); 32 | 33 | router.get("/genres").handler(this::allGenres); 34 | router.get("/albums").handler(this::allAlbums); 35 | router.get("/album/:id").handler(this::album); 36 | router.get("/album/:id/tracks").handler(this::tracks); 37 | router.route().failureHandler(ErrorHandler.create()); 38 | 39 | vertx.createHttpServer() 40 | .requestHandler(router) 41 | .listen(8081); 42 | } 43 | 44 | private void setResponseContentType(RoutingContext rc) { 45 | rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json"); 46 | rc.next(); 47 | } 48 | 49 | private void allGenres(RoutingContext rc) { 50 | rc.response().end(dataRepository.genres.toBuffer()); 51 | } 52 | 53 | private void allAlbums(RoutingContext rc) { 54 | List filter = rc.queryParam("genre").stream().map(Integer::parseInt).collect(toList()); 55 | if (filter.isEmpty()) { 56 | rc.response().end(dataRepository.albums.toBuffer()); 57 | } else { 58 | JsonArray filtered = dataRepository.albums.stream() 59 | .map(JsonObject.class::cast) 60 | .filter(album -> filter.contains(album.getJsonObject("genre").getInteger("id"))) 61 | .collect(JsonArray::new, JsonArray::add, JsonArray::addAll); 62 | rc.response().end(filtered.toBuffer()); 63 | } 64 | } 65 | 66 | private void album(RoutingContext rc) { 67 | boolean withTracks = rc.queryParams().contains("withTracks"); 68 | Optional album = dataRepository.albums.stream() 69 | .map(JsonObject.class::cast) 70 | .filter(json -> json.getInteger("id").equals(Integer.parseInt(rc.pathParam("id")))) 71 | .findFirst() 72 | .map(json -> withTracks ? json.copy().put("tracks", dataRepository.tracksByAlbum.get(json.getInteger("id"))) : json); 73 | if (album.isPresent()) { 74 | rc.response().end(album.get().toBuffer()); 75 | } else { 76 | rc.fail(404); 77 | } 78 | } 79 | 80 | private void tracks(RoutingContext rc) { 81 | JsonArray tracks = dataRepository.tracksByAlbum.get(Integer.parseInt(rc.pathParam("id"))); 82 | if (tracks != null) { 83 | rc.response().end(tracks.toBuffer()); 84 | } else { 85 | rc.fail(404); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /inventory/src/main/resources/data/albums.csv: -------------------------------------------------------------------------------- 1 | id,name ,genre,artist ,image 2 | 1 ,Illmatic ,3 ,Nas ,illmatic.png 3 | 2 ,Enter the Wu-Tang (36 Chambers),3 ,Wu-Tang Clan ,enter-the-wu-tang.png 4 | 3 ,Ready To Die ,3 ,Notorious B.I.G,ready-to-die.png 5 | 4 ,The Marshall Mathers LP ,3 ,Eminem ,the-marshall-mathers-lp.png 6 | 5 ,Revolver ,1 ,The Beatles ,revolver.png 7 | 6 ,Automatic for the People ,1 ,R.E.M ,automatic-for-the-people.png 8 | 7 ,Achtung Baby ,1 ,U2 ,achtung-baby.png 9 | 8 ,Thriller ,1 ,Michael Jackson,thriller.png 10 | 9 ,Dark Side Of The Moon ,2 ,Pink Floyd ,dark-side-of-the-moon.png 11 | 10,Appetite For Destruction ,2 ,Guns N' Roses ,appetite-for-destruction.png 12 | 11,Back In Black ,2 ,AC/DC ,back-in-black.png 13 | 12,Master Of Puppets ,2 ,Metallica ,master-of-puppets.png 14 | -------------------------------------------------------------------------------- /inventory/src/main/resources/data/genres.csv: -------------------------------------------------------------------------------- 1 | id,name ,image 2 | 1 ,Pop ,pop.png 3 | 2 ,Rock ,rock.png 4 | 3 ,Hip hop,hip-hop.png 5 | -------------------------------------------------------------------------------- /inventory/src/main/resources/vertx-default-jul-logging.properties: -------------------------------------------------------------------------------- 1 | handlers=java.util.logging.ConsoleHandler 2 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 3 | java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n 4 | java.util.logging.ConsoleHandler.level=INFO 5 | .level=INFO 6 | io.vertx.level=INFO 7 | io.netty.util.internal.PlatformDependent.level=SEVERE 8 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | graphql-api-gateway-workshop 8 | parent 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | 1.8 14 | 1.8 15 | UTF-8 16 | 17 | 3.8.4 18 | 1.0.23 19 | 20 | 21 | 22 | common 23 | client 24 | inventory 25 | rating 26 | gateway 27 | steps 28 | 29 | 30 | 31 | 32 | 33 | io.reactivex.rxjava2 34 | rxjava 35 | 2.2.14 36 | 37 | 38 | com.github.akarnokd 39 | rxjava2-jdk8-interop 40 | 0.3.7 41 | 42 | 43 | org.slf4j 44 | slf4j-jdk14 45 | 1.7.28 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-compiler-plugin 56 | 3.8.1 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-surefire-plugin 61 | 2.22.2 62 | 63 | 64 | io.reactiverse 65 | vertx-maven-plugin 66 | ${vertx-maven-plugin.version} 67 | 68 | 69 | vmp 70 | 71 | initialize 72 | package 73 | 74 | 75 | 76 | 77 | true 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /rating/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | rating 14 | 15 | 16 | workshop.rating.RatingServer 17 | 18 | 19 | 20 | 21 | 22 | io.vertx 23 | vertx-stack-depchain 24 | ${vertx.version} 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | 33 | io.vertx 34 | vertx-web 35 | 36 | 37 | 38 | 39 | ${project.artifactId} 40 | 41 | 42 | io.reactiverse 43 | vertx-maven-plugin 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /rating/run.bat: -------------------------------------------------------------------------------- 1 | java -jar target\rating.jar 2 | -------------------------------------------------------------------------------- /rating/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | java -jar target/rating.jar 6 | -------------------------------------------------------------------------------- /rating/src/main/java/workshop/rating/RatingServer.java: -------------------------------------------------------------------------------- 1 | package workshop.rating; 2 | 3 | import io.vertx.core.AbstractVerticle; 4 | import io.vertx.core.http.HttpHeaders; 5 | import io.vertx.core.json.JsonArray; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.ext.web.Router; 8 | import io.vertx.ext.web.RoutingContext; 9 | import io.vertx.ext.web.handler.BodyHandler; 10 | import io.vertx.ext.web.handler.ErrorHandler; 11 | import io.vertx.ext.web.handler.LoggerFormat; 12 | import io.vertx.ext.web.handler.LoggerHandler; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import static java.util.stream.Collectors.averagingInt; 18 | 19 | public class RatingServer extends AbstractVerticle { 20 | 21 | private final Map reviewsByAlbum = new HashMap<>(); 22 | 23 | @Override 24 | public void start() throws Exception { 25 | 26 | Router router = Router.router(vertx); 27 | 28 | router.route().handler(LoggerHandler.create(LoggerFormat.TINY)); 29 | 30 | router.route().handler(this::setResponseContentType); 31 | 32 | router.get("/album/:id").handler(this::dataToContext).handler(this::ratingAndReviews); 33 | router.get("/album/:id/rating").handler(this::dataToContext).handler(this::rating); 34 | router.get("/album/:id/reviews").handler(this::dataToContext).handler(this::reviews); 35 | router.post("/album/:id/reviews").handler(this::dataToContext).handler(BodyHandler.create()).handler(this::addReview); 36 | 37 | router.route().failureHandler(ErrorHandler.create()); 38 | 39 | vertx.createHttpServer() 40 | .requestHandler(router) 41 | .listen(8082); 42 | } 43 | 44 | private void setResponseContentType(RoutingContext rc) { 45 | rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json"); 46 | rc.next(); 47 | } 48 | 49 | private void dataToContext(RoutingContext rc) { 50 | Integer id = Integer.valueOf(rc.pathParam("id")); 51 | rc.put("albumId", id); 52 | rc.put("reviews", reviewsByAlbum.computeIfAbsent(rc.get("albumId"), k -> new JsonArray())); 53 | rc.next(); 54 | } 55 | 56 | private void ratingAndReviews(RoutingContext rc) { 57 | JsonArray reviews = rc.get("reviews"); 58 | rc.response().end(new JsonObject().put("rating", avgRating(reviews)).put("reviews", reviews).toBuffer()); 59 | } 60 | 61 | private void rating(RoutingContext rc) { 62 | JsonArray reviews = rc.get("reviews"); 63 | int rating = avgRating(reviews); 64 | rc.response().end(new JsonObject().put("value", rating).toBuffer()); 65 | } 66 | 67 | private void reviews(RoutingContext rc) { 68 | JsonArray reviews = rc.get("reviews"); 69 | rc.response().end(reviews.toBuffer()); 70 | } 71 | 72 | private void addReview(RoutingContext rc) { 73 | JsonObject submitted = rc.getBodyAsJson(); 74 | JsonArray reviews = rc.get("reviews"); 75 | reviews.add(submitted); 76 | ratingAndReviews(rc); 77 | } 78 | 79 | private Integer avgRating(JsonArray reviews) { 80 | double rating = reviews.stream() 81 | .map(JsonObject.class::cast) 82 | .collect(averagingInt(review -> review.getInteger("rating"))); 83 | return (int) Math.round(rating); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /rating/src/main/resources/vertx-default-jul-logging.properties: -------------------------------------------------------------------------------- 1 | handlers=java.util.logging.ConsoleHandler 2 | java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter 3 | java.util.logging.SimpleFormatter.format=[%1$tF %1$tT] [%4$-7s] %5$s %n 4 | java.util.logging.ConsoleHandler.level=INFO 5 | .level=INFO 6 | io.vertx.level=INFO 7 | io.netty.util.internal.PlatformDependent.level=SEVERE 8 | -------------------------------------------------------------------------------- /run-postgres.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | docker run -p 5432:5432 -e POSTGRES_USER=musicstore -e POSTGRES_PASSWORD=musicstore -d postgres 6 | -------------------------------------------------------------------------------- /steps/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | parent 10 | 1.0-SNAPSHOT 11 | 12 | 13 | steps 14 | pom 15 | 16 | 17 | main 18 | 19 | 20 | 21 | step-0 22 | step-1 23 | step-2 24 | step-3 25 | step-4 26 | step-5 27 | step-6 28 | step-7 29 | 30 | 31 | 32 | 33 | 34 | io.vertx 35 | vertx-stack-depchain 36 | ${vertx.version} 37 | pom 38 | import 39 | 40 | 41 | 42 | 43 | 44 | 45 | graphql-api-gateway-workshop 46 | common 47 | ${project.version} 48 | 49 | 50 | graphql-api-gateway-workshop 51 | client 52 | ${project.version} 53 | 54 | 55 | io.vertx 56 | vertx-rx-java2 57 | 58 | 59 | com.github.akarnokd 60 | rxjava2-jdk8-interop 61 | 62 | 63 | io.vertx 64 | vertx-web-graphql 65 | 66 | 67 | io.vertx 68 | vertx-auth-htpasswd 69 | 70 | 71 | io.vertx 72 | vertx-web-client 73 | 74 | 75 | io.vertx 76 | vertx-pg-client 77 | 78 | 79 | org.slf4j 80 | slf4j-jdk14 81 | 82 | 83 | 84 | 85 | 86 | solution 87 | 88 | solution 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /steps/step-0/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-0 14 | 15 | 16 | workshop.gateway.Step0Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | io.reactiverse 25 | vertx-maven-plugin 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /steps/step-0/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-0.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-0/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-0.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-0/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-0/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-0/src/main/java/workshop/gateway/Step0Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import io.vertx.reactivex.ext.web.Router; 4 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 5 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 6 | 7 | public class Step0Server extends WorkshopVerticle { 8 | 9 | protected Router createRouter() { 10 | Router router = Router.router(vertx); 11 | 12 | // TODO: create a route for GET requests and set the StaticHandlera 13 | 14 | // TODO: create a generic route and set ErrorHandler as the failureHandler 15 | 16 | return router; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /steps/step-0/src/solution/java/workshop/gateway/Step0Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import io.vertx.reactivex.ext.web.Router; 4 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 5 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 6 | 7 | public class Step0Server extends WorkshopVerticle { 8 | 9 | protected Router createRouter() { 10 | Router router = Router.router(vertx); 11 | 12 | router.get().handler(StaticHandler.create()); 13 | 14 | router.route().failureHandler(ErrorHandler.create()); 15 | 16 | return router; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /steps/step-1/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-1 14 | 15 | 16 | workshop.gateway.Step1Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-1/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-1.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-1/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-1.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-1/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-1/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-1/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | // TODO: return all genres with genresRepository#findAll 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-1/src/main/java/workshop/gateway/Step1Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step1Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | // TODO: create GraphQL runtime with setupGraphQLJava method 21 | // TODO: define route for /graphql requests and set the GraphQLHandler 22 | 23 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 24 | 25 | router.get().handler(StaticHandler.create()); 26 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 27 | 28 | router.route().failureHandler(ErrorHandler.create()); 29 | 30 | return router; 31 | } 32 | 33 | protected RuntimeWiring runtimeWiring() { 34 | return RuntimeWiring.newRuntimeWiring() 35 | .type("Query", this::query) 36 | .build(); 37 | } 38 | 39 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 40 | return builder 41 | // TODO: add data fetcher for the genres field of the root Query type 42 | ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /steps/step-1/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | # TODO: add id field of type Int! 3 | # TODO: add name field of type String! 4 | # TODO: add image field of type String 5 | } 6 | 7 | type Query { 8 | # TODO: add genres *field* of type [Genre!] 9 | } 10 | 11 | schema { 12 | query: Query 13 | } 14 | -------------------------------------------------------------------------------- /steps/step-1/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-1/src/solution/java/workshop/gateway/Step1Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step1Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | 23 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 24 | 25 | router.get().handler(StaticHandler.create()); 26 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 27 | 28 | router.route().failureHandler(ErrorHandler.create()); 29 | 30 | return router; 31 | } 32 | 33 | protected RuntimeWiring runtimeWiring() { 34 | return RuntimeWiring.newRuntimeWiring() 35 | .type("Query", this::query) 36 | .build(); 37 | } 38 | 39 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 40 | return builder 41 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 42 | ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /steps/step-1/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Query { 8 | genres: [Genre!] 9 | } 10 | 11 | schema { 12 | query: Query 13 | } 14 | -------------------------------------------------------------------------------- /steps/step-2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-2 14 | 15 | 16 | workshop.gateway.Step2Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-2/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-2.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-2/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-2.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-2/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-2/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-2/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | // TODO: get genre argument from env and find albums 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-2/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-2/src/main/java/workshop/gateway/Step2Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step2Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 23 | 24 | router.get().handler(StaticHandler.create()); 25 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 26 | 27 | router.route().failureHandler(ErrorHandler.create()); 28 | 29 | return router; 30 | } 31 | 32 | protected RuntimeWiring runtimeWiring() { 33 | return RuntimeWiring.newRuntimeWiring() 34 | .type("Query", this::query) 35 | .build(); 36 | } 37 | 38 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 39 | return builder 40 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 41 | // TODO: define the AlbumsDataFetcher for the albums field of the Query type 42 | ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /steps/step-2/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Album { 8 | # TODO: add id field of type Int! 9 | # TODO: add name field of type String! 10 | # TODO: add genre field of type Genre! 11 | # TODO: add artist field of type String! 12 | # TODO: add image field of type String 13 | } 14 | 15 | type Query { 16 | genres: [Genre!] 17 | # TODO: add albums field with param genre of type Int 18 | } 19 | 20 | schema { 21 | query: Query 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-2/src/solution/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-2/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-2/src/solution/java/workshop/gateway/Step2Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step2Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 23 | 24 | router.get().handler(StaticHandler.create()); 25 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 26 | 27 | router.route().failureHandler(ErrorHandler.create()); 28 | 29 | return router; 30 | } 31 | 32 | protected RuntimeWiring runtimeWiring() { 33 | return RuntimeWiring.newRuntimeWiring() 34 | .type("Query", this::query) 35 | .build(); 36 | } 37 | 38 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 39 | return builder 40 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 41 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 42 | ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /steps/step-2/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Album { 8 | id: Int! 9 | name: String! 10 | genre: Genre! 11 | artist: String! 12 | image: String 13 | } 14 | 15 | type Query { 16 | genres: [Genre!] 17 | albums(genre: Int): [Album!] 18 | } 19 | 20 | schema { 21 | query: Query 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-3/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-3 14 | 15 | 16 | workshop.gateway.Step3Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-3/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-3.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-3/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-3.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-3/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-3/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-3/src/main/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | // TODO: find album by id with tracks included 24 | // TODO: find rating info by id 25 | // TODO: use Single#zipWith operator for concurrent requests execution and merge results 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /steps/step-3/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-3/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-3/src/main/java/workshop/gateway/Step3Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step3Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 23 | 24 | router.get().handler(StaticHandler.create()); 25 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 26 | 27 | router.route().failureHandler(ErrorHandler.create()); 28 | 29 | return router; 30 | } 31 | 32 | protected RuntimeWiring runtimeWiring() { 33 | return RuntimeWiring.newRuntimeWiring() 34 | .type("Query", this::query) 35 | .build(); 36 | } 37 | 38 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 39 | return builder 40 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 41 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 42 | .dataFetcher("album", new AlbumDataFetcher(albumsRepository, ratingRepository)) 43 | ; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /steps/step-3/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | } 34 | 35 | schema { 36 | query: Query 37 | } 38 | -------------------------------------------------------------------------------- /steps/step-3/src/solution/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-3/src/solution/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-3/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-3/src/solution/java/workshop/gateway/Step3Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step3Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 23 | 24 | router.get().handler(StaticHandler.create()); 25 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 26 | 27 | router.route().failureHandler(ErrorHandler.create()); 28 | 29 | return router; 30 | } 31 | 32 | protected RuntimeWiring runtimeWiring() { 33 | return RuntimeWiring.newRuntimeWiring() 34 | .type("Query", this::query) 35 | .build(); 36 | } 37 | 38 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 39 | return builder 40 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 41 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 42 | .dataFetcher("album", new AlbumDataFetcher(albumsRepository, ratingRepository)) 43 | ; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /steps/step-3/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | } 34 | 35 | schema { 36 | query: Query 37 | } 38 | -------------------------------------------------------------------------------- /steps/step-4/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-4 14 | 15 | 16 | workshop.gateway.Step4Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-4/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-4.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-4/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-4.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-4/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-4/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | // TODO: retrieve source Album from env 22 | // TODO: if tracks are set, return right away with Single#just 23 | // TODO: otherwise, find tracks by album id 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-4/src/main/java/workshop/gateway/Step4Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step4Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 23 | 24 | router.get().handler(StaticHandler.create()); 25 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 26 | 27 | router.route().failureHandler(ErrorHandler.create()); 28 | 29 | return router; 30 | } 31 | 32 | protected RuntimeWiring runtimeWiring() { 33 | return RuntimeWiring.newRuntimeWiring() 34 | .type("Query", this::query) 35 | // TODO: define Album runtime wiring in the album method 36 | .build(); 37 | } 38 | 39 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 40 | return builder 41 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 42 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 43 | .dataFetcher("album", new AlbumDataFetcher(albumsRepository, ratingRepository)); 44 | } 45 | 46 | private TypeRuntimeWiring.Builder album(TypeRuntimeWiring.Builder builder) { 47 | return builder 48 | .dataFetcher("tracks", new AlbumTracksDataFetcher(tracksRepository)) 49 | .dataFetcher("rating", new AlbumRatingDataFetcher(ratingRepository)) 50 | .dataFetcher("reviews", new AlbumReviewsDataFetcher(ratingRepository)) 51 | ; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /steps/step-4/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | } 34 | 35 | schema { 36 | query: Query 37 | } 38 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/java/workshop/gateway/Step4Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.handler.BodyHandler; 8 | import io.vertx.reactivex.ext.web.handler.ErrorHandler; 9 | import io.vertx.reactivex.ext.web.handler.StaticHandler; 10 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 12 | 13 | public class Step4Server extends WorkshopVerticle { 14 | 15 | protected Router createRouter() { 16 | Router router = Router.router(vertx); 17 | 18 | router.route().handler(BodyHandler.create()); 19 | 20 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 21 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 22 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 23 | 24 | router.get().handler(StaticHandler.create()); 25 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 26 | 27 | router.route().failureHandler(ErrorHandler.create()); 28 | 29 | return router; 30 | } 31 | 32 | protected RuntimeWiring runtimeWiring() { 33 | return RuntimeWiring.newRuntimeWiring() 34 | .type("Query", this::query) 35 | .type("Album", this::album) 36 | .build(); 37 | } 38 | 39 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 40 | return builder 41 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 42 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 43 | .dataFetcher("album", new AlbumDataFetcher(albumsRepository, ratingRepository)); 44 | } 45 | 46 | private TypeRuntimeWiring.Builder album(TypeRuntimeWiring.Builder builder) { 47 | return builder 48 | .dataFetcher("tracks", new AlbumTracksDataFetcher(tracksRepository)) 49 | .dataFetcher("rating", new AlbumRatingDataFetcher(ratingRepository)) 50 | .dataFetcher("reviews", new AlbumReviewsDataFetcher(ratingRepository)) 51 | ; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /steps/step-4/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | } 34 | 35 | schema { 36 | query: Query 37 | } 38 | -------------------------------------------------------------------------------- /steps/step-5/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-5 14 | 15 | 16 | workshop.gateway.Step5Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-5/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-5.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-5/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-5.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-5/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-5/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/Step5Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.core.http.HttpHeaders; 7 | import io.vertx.ext.auth.htpasswd.HtpasswdAuthOptions; 8 | import io.vertx.reactivex.ext.auth.htpasswd.HtpasswdAuth; 9 | import io.vertx.reactivex.ext.web.Router; 10 | import io.vertx.reactivex.ext.web.handler.*; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 12 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 13 | import io.vertx.reactivex.ext.web.sstore.LocalSessionStore; 14 | 15 | public class Step5Server extends WorkshopVerticle { 16 | 17 | protected Router createRouter() { 18 | Router router = Router.router(vertx); 19 | 20 | router.route().handler(BodyHandler.create()); 21 | 22 | HtpasswdAuthOptions authOptions = new HtpasswdAuthOptions() 23 | .setHtpasswdFile("passwordfile") 24 | .setPlainTextEnabled(true); 25 | HtpasswdAuth authProvider = HtpasswdAuth.create(vertx, authOptions); 26 | 27 | SessionHandler sessionHandler = SessionHandler.create(LocalSessionStore.create(vertx)).setAuthProvider(authProvider); 28 | // TODO: add generic route and set sessionHandler 29 | FormLoginHandler formLoginHandler = FormLoginHandler.create(authProvider).setDirectLoggedInOKURL("/"); 30 | // TODO: add post route to /login.html and set formLoginHandler 31 | router.get("/logout").handler(rc -> { 32 | // TODO: clear user from RoutingContext 33 | // TODO: get session and destroy it 34 | rc.response().setStatusCode(307).putHeader(HttpHeaders.LOCATION, "/").end(); 35 | }); 36 | 37 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 38 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 39 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 40 | 41 | router.get().handler(StaticHandler.create()); 42 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 43 | 44 | router.route().failureHandler(ErrorHandler.create()); 45 | 46 | return router; 47 | } 48 | 49 | protected RuntimeWiring runtimeWiring() { 50 | return RuntimeWiring.newRuntimeWiring() 51 | .type("Query", this::query) 52 | .type("Album", this::album) 53 | .build(); 54 | } 55 | 56 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 57 | return builder 58 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 59 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 60 | .dataFetcher("album", new AlbumDataFetcher(albumsRepository, ratingRepository)) 61 | .dataFetcher("currentUser", new CurrentUserDataFetcher()); 62 | } 63 | 64 | private TypeRuntimeWiring.Builder album(TypeRuntimeWiring.Builder builder) { 65 | return builder 66 | .dataFetcher("tracks", new AlbumTracksDataFetcher(tracksRepository)) 67 | .dataFetcher("rating", new AlbumRatingDataFetcher(ratingRepository)) 68 | .dataFetcher("reviews", new AlbumReviewsDataFetcher(ratingRepository)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /steps/step-5/src/main/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | // TODO: get RoutingContext from env#getContext 11 | // TODO: get user from RoutingContext 12 | // TODO: if user is not null, get the principal and return username, otherwise return null 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /steps/step-5/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | currentUser: String 34 | } 35 | 36 | schema { 37 | query: Query 38 | } 39 | -------------------------------------------------------------------------------- /steps/step-5/src/main/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/Step5Server.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.GraphQL; 4 | import graphql.schema.idl.RuntimeWiring; 5 | import graphql.schema.idl.TypeRuntimeWiring; 6 | import io.vertx.core.http.HttpHeaders; 7 | import io.vertx.ext.auth.htpasswd.HtpasswdAuthOptions; 8 | import io.vertx.reactivex.ext.auth.htpasswd.HtpasswdAuth; 9 | import io.vertx.reactivex.ext.web.Router; 10 | import io.vertx.reactivex.ext.web.handler.*; 11 | import io.vertx.reactivex.ext.web.handler.graphql.GraphQLHandler; 12 | import io.vertx.reactivex.ext.web.handler.graphql.GraphiQLHandler; 13 | import io.vertx.reactivex.ext.web.sstore.LocalSessionStore; 14 | 15 | public class Step5Server extends WorkshopVerticle { 16 | 17 | protected Router createRouter() { 18 | Router router = Router.router(vertx); 19 | 20 | router.route().handler(BodyHandler.create()); 21 | 22 | HtpasswdAuthOptions authOptions = new HtpasswdAuthOptions() 23 | .setHtpasswdFile("passwordfile") 24 | .setPlainTextEnabled(true); 25 | HtpasswdAuth authProvider = HtpasswdAuth.create(vertx, authOptions); 26 | 27 | SessionHandler sessionHandler = SessionHandler.create(LocalSessionStore.create(vertx)).setAuthProvider(authProvider); 28 | router.route().handler(sessionHandler); 29 | FormLoginHandler formLoginHandler = FormLoginHandler.create(authProvider).setDirectLoggedInOKURL("/"); 30 | router.post("/login.html").handler(formLoginHandler); 31 | router.get("/logout").handler(rc -> { 32 | rc.clearUser(); 33 | rc.session().destroy(); 34 | rc.response().setStatusCode(307).putHeader(HttpHeaders.LOCATION, "/").end(); 35 | }); 36 | 37 | GraphQL graphQL = setupGraphQLJava("musicstore.graphql"); 38 | router.route("/graphql").handler(GraphQLHandler.create(graphQL)); 39 | router.get("/graphiql/*").handler(GraphiQLHandler.create()); 40 | 41 | router.get().handler(StaticHandler.create()); 42 | router.get().handler(WorkshopVerticle::rerouteToVueIndex); 43 | 44 | router.route().failureHandler(ErrorHandler.create()); 45 | 46 | return router; 47 | } 48 | 49 | protected RuntimeWiring runtimeWiring() { 50 | return RuntimeWiring.newRuntimeWiring() 51 | .type("Query", this::query) 52 | .type("Album", this::album) 53 | .build(); 54 | } 55 | 56 | private TypeRuntimeWiring.Builder query(TypeRuntimeWiring.Builder builder) { 57 | return builder 58 | .dataFetcher("genres", new GenresDataFetcher(genresRepository)) 59 | .dataFetcher("albums", new AlbumsDataFetcher(albumsRepository)) 60 | .dataFetcher("album", new AlbumDataFetcher(albumsRepository, ratingRepository)) 61 | .dataFetcher("currentUser", new CurrentUserDataFetcher()); 62 | } 63 | 64 | private TypeRuntimeWiring.Builder album(TypeRuntimeWiring.Builder builder) { 65 | return builder 66 | .dataFetcher("tracks", new AlbumTracksDataFetcher(tracksRepository)) 67 | .dataFetcher("rating", new AlbumRatingDataFetcher(ratingRepository)) 68 | .dataFetcher("reviews", new AlbumReviewsDataFetcher(ratingRepository)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | RoutingContext routingContext = env.getContext(); 11 | User user = routingContext.user(); 12 | return user!=null ? user.principal().getString("username"):null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | currentUser: String 34 | } 35 | 36 | schema { 37 | query: Query 38 | } 39 | -------------------------------------------------------------------------------- /steps/step-5/src/solution/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /steps/step-6/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-6 14 | 15 | 16 | workshop.gateway.Step6Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-6/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-6.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-6/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-6.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-6/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-6/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/AddReviewDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.RatingInfo; 6 | import workshop.model.ReviewInput; 7 | import workshop.repository.RatingRepository; 8 | 9 | public class AddReviewDataFetcher implements RxDataFetcher { 10 | 11 | private final RatingRepository ratingRepository; 12 | 13 | public AddReviewDataFetcher(RatingRepository ratingRepository) { 14 | this.ratingRepository = ratingRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | String currentUser = UserUtil.currentUser(env); 20 | if (currentUser==null) { 21 | throw new NotLoggedInException(); 22 | } 23 | Integer albumId = env.getArgument("albumId"); 24 | // TODO: retrieve ReviewInput with EnvironmentUtil#getInputArgument 25 | // TODO: set currentUser in ReviewInput 26 | // TODO: add ReviewInput with ratingRepository#addReview and return RatingInfo 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-6/src/main/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | RoutingContext routingContext = env.getContext(); 11 | User user = routingContext.user(); 12 | return user!=null ? user.principal().getString("username"):null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /steps/step-6/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | currentUser: String 34 | } 35 | 36 | input ReviewInput { 37 | # TODO: add rating field of type Int! 38 | # TODO: add comment field of type String 39 | } 40 | 41 | type ReviewResult { 42 | rating: Int! 43 | reviews: [Review!]! 44 | } 45 | 46 | type Mutation { 47 | addReview(albumId: Int!, review: ReviewInput): ReviewResult 48 | } 49 | 50 | schema { 51 | query: Query 52 | mutation: Mutation 53 | } 54 | -------------------------------------------------------------------------------- /steps/step-6/src/main/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/AddReviewDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.RatingInfo; 6 | import workshop.model.ReviewInput; 7 | import workshop.repository.RatingRepository; 8 | 9 | public class AddReviewDataFetcher implements RxDataFetcher { 10 | 11 | private final RatingRepository ratingRepository; 12 | 13 | public AddReviewDataFetcher(RatingRepository ratingRepository) { 14 | this.ratingRepository = ratingRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | String currentUser = UserUtil.currentUser(env); 20 | if (currentUser==null) { 21 | throw new NotLoggedInException(); 22 | } 23 | Integer albumId = env.getArgument("albumId"); 24 | ReviewInput reviewInput = EnvironmentUtil.getInputArgument(env, "review", ReviewInput.class); 25 | reviewInput.setName(currentUser); 26 | return ratingRepository.addReview(albumId, reviewInput); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | RoutingContext routingContext = env.getContext(); 11 | User user = routingContext.user(); 12 | return user!=null ? user.principal().getString("username"):null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type Query { 30 | genres: [Genre!] 31 | albums(genre: Int): [Album!] 32 | album(id: Int!): Album 33 | currentUser: String 34 | } 35 | 36 | input ReviewInput { 37 | rating: Int! 38 | comment: String 39 | } 40 | 41 | type ReviewResult { 42 | rating: Int! 43 | reviews: [Review!]! 44 | } 45 | 46 | type Mutation { 47 | addReview(albumId: Int!, review: ReviewInput): ReviewResult 48 | } 49 | 50 | schema { 51 | query: Query 52 | mutation: Mutation 53 | } 54 | -------------------------------------------------------------------------------- /steps/step-6/src/solution/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /steps/step-7/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | 8 | graphql-api-gateway-workshop 9 | steps 10 | 1.0-SNAPSHOT 11 | 12 | 13 | step-7 14 | 15 | 16 | workshop.gateway.Step7Server 17 | 18 | 19 | 20 | ${project.artifactId} 21 | src/${sources.root}/java 22 | 23 | 24 | src/${sources.root}/resources 25 | 26 | 27 | 28 | 29 | io.reactiverse 30 | vertx-maven-plugin 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /steps/step-7/run-solution.bat: -------------------------------------------------------------------------------- 1 | mvn clean package -Psolution 2 | java -jar target\step-7.jar -Dvertxweb.environment=dev 3 | -------------------------------------------------------------------------------- /steps/step-7/run-solution.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean package -Psolution 6 | java -jar target/step-7.jar -Dvertxweb.environment=dev 7 | -------------------------------------------------------------------------------- /steps/step-7/run.bat: -------------------------------------------------------------------------------- 1 | mvn clean vertx:run 2 | -------------------------------------------------------------------------------- /steps/step-7/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | mvn clean vertx:run 6 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AddReviewDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.RatingInfo; 6 | import workshop.model.ReviewInput; 7 | import workshop.repository.RatingRepository; 8 | 9 | public class AddReviewDataFetcher implements RxDataFetcher { 10 | 11 | private final RatingRepository ratingRepository; 12 | 13 | public AddReviewDataFetcher(RatingRepository ratingRepository) { 14 | this.ratingRepository = ratingRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | String currentUser = UserUtil.currentUser(env); 20 | if (currentUser==null) { 21 | throw new NotLoggedInException(); 22 | } 23 | Integer albumId = env.getArgument("albumId"); 24 | ReviewInput reviewInput = EnvironmentUtil.getInputArgument(env, "review", ReviewInput.class); 25 | reviewInput.setName(currentUser); 26 | return ratingRepository.addReview(albumId, reviewInput); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AddToCartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class AddToCartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public AddToCartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | String currentUser = UserUtil.currentUser(env); 19 | if (currentUser==null) { 20 | throw new NotLoggedInException(); 21 | } 22 | Integer albumId = env.getArgument("albumId"); 23 | return cartRepository.addToCart(currentUser, albumId).andThen(cartRepository.findCart(currentUser)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/CartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class CartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public CartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) { 18 | String curentUser = UserUtil.currentUser(env); 19 | return curentUser==null ? null:cartRepository.findCart(curentUser); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/CartItemAlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.CartItem; 7 | import workshop.repository.AlbumsRepository; 8 | 9 | public class CartItemAlbumDataFetcher implements RxDataFetcher { 10 | 11 | private final AlbumsRepository albumsRepository; 12 | 13 | public CartItemAlbumDataFetcher(AlbumsRepository albumsRepository) { 14 | this.albumsRepository = albumsRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | // TODO: retrieve source CartItem from env 20 | // TODO: find corresponding album using albumsRepository#findById *without loading tracks* 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/RemoveFromCartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class RemoveFromCartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public RemoveFromCartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | String currentUser = UserUtil.currentUser(env); 19 | if (currentUser==null) { 20 | throw new NotLoggedInException(); 21 | } 22 | Integer albumId = env.getArgument("albumId"); 23 | return cartRepository.removeFromCart(currentUser, albumId).andThen(cartRepository.findCart(currentUser)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /steps/step-7/src/main/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | RoutingContext routingContext = env.getContext(); 11 | User user = routingContext.user(); 12 | return user!=null ? user.principal().getString("username"):null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /steps/step-7/src/main/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type CartItem { 30 | # TODO add album field of type Album 31 | quantity: Int 32 | } 33 | 34 | type Cart { 35 | items: [CartItem!] 36 | } 37 | 38 | type Query { 39 | genres: [Genre!] 40 | albums(genre: Int): [Album!] 41 | album(id: Int!): Album 42 | cart: Cart 43 | currentUser: String 44 | } 45 | 46 | input ReviewInput { 47 | rating: Int! 48 | comment: String 49 | } 50 | 51 | type ReviewResult { 52 | rating: Int! 53 | reviews: [Review!]! 54 | } 55 | 56 | type Mutation { 57 | addToCart(albumId: Int!): Cart 58 | removeFromCart(albumId: Int!): Cart 59 | addReview(albumId: Int!, review: ReviewInput): ReviewResult 60 | } 61 | 62 | schema { 63 | query: Query 64 | mutation: Mutation 65 | } 66 | -------------------------------------------------------------------------------- /steps/step-7/src/main/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AddReviewDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.RatingInfo; 6 | import workshop.model.ReviewInput; 7 | import workshop.repository.RatingRepository; 8 | 9 | public class AddReviewDataFetcher implements RxDataFetcher { 10 | 11 | private final RatingRepository ratingRepository; 12 | 13 | public AddReviewDataFetcher(RatingRepository ratingRepository) { 14 | this.ratingRepository = ratingRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | String currentUser = UserUtil.currentUser(env); 20 | if (currentUser==null) { 21 | throw new NotLoggedInException(); 22 | } 23 | Integer albumId = env.getArgument("albumId"); 24 | ReviewInput reviewInput = EnvironmentUtil.getInputArgument(env, "review", ReviewInput.class); 25 | reviewInput.setName(currentUser); 26 | return ratingRepository.addReview(albumId, reviewInput); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AddToCartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class AddToCartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public AddToCartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | String currentUser = UserUtil.currentUser(env); 19 | if (currentUser==null) { 20 | throw new NotLoggedInException(); 21 | } 22 | Integer albumId = env.getArgument("albumId"); 23 | return cartRepository.addToCart(currentUser, albumId).andThen(cartRepository.findCart(currentUser)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.RatingInfo; 7 | import workshop.repository.AlbumsRepository; 8 | import workshop.repository.RatingRepository; 9 | 10 | public class AlbumDataFetcher implements RxDataFetcher { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumDataFetcher(AlbumsRepository albumsRepository, RatingRepository ratingRepository) { 16 | this.albumsRepository = albumsRepository; 17 | this.ratingRepository = ratingRepository; 18 | } 19 | 20 | @Override 21 | public Single rxGet(DataFetchingEnvironment env) { 22 | Integer id = env.getArgument("id"); 23 | Single inventoryData = albumsRepository.findById(id, true); 24 | Single ratingData = ratingRepository.findRatingAndReviewsByAlbum(id); 25 | return inventoryData.zipWith(ratingData, (a, r) -> { 26 | a.setRating(r.getRating()); 27 | a.setReviews(r.getReviews()); 28 | return a; 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AlbumRatingDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.RatingRepository; 7 | 8 | public class AlbumRatingDataFetcher implements RxDataFetcher { 9 | 10 | private final RatingRepository ratingRepository; 11 | 12 | public AlbumRatingDataFetcher(RatingRepository ratingRepository) { 13 | this.ratingRepository = ratingRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | Album album = env.getSource(); 19 | Single rating; 20 | if (album.getRating()!=null) { 21 | rating = Single.just(album.getRating()); 22 | } else { 23 | rating = ratingRepository.findRatingByAlbum(album.getId()); 24 | } 25 | return rating; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AlbumReviewsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Review; 7 | import workshop.repository.RatingRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumReviewsDataFetcher implements RxDataFetcher> { 12 | 13 | private final RatingRepository ratingRepository; 14 | 15 | public AlbumReviewsDataFetcher(RatingRepository ratingRepository) { 16 | this.ratingRepository = ratingRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> reviews; 23 | if (album.getReviews()!=null) { 24 | reviews = Single.just(album.getReviews()); 25 | } else { 26 | reviews = ratingRepository.findReviewsByAlbum(album.getId()); 27 | } 28 | return reviews; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AlbumTracksDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.Track; 7 | import workshop.repository.TracksRepository; 8 | 9 | import java.util.List; 10 | 11 | public class AlbumTracksDataFetcher implements RxDataFetcher> { 12 | 13 | private final TracksRepository tracksRepository; 14 | 15 | public AlbumTracksDataFetcher(TracksRepository tracksRepository) { 16 | this.tracksRepository = tracksRepository; 17 | } 18 | 19 | @Override 20 | public Single> rxGet(DataFetchingEnvironment env) throws Exception { 21 | Album album = env.getSource(); 22 | Single> tracks; 23 | if (album.getTracks()!=null) { 24 | tracks = Single.just(album.getTracks()); 25 | } else { 26 | tracks = tracksRepository.findByAlbum(album.getId()); 27 | } 28 | return tracks; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/AlbumsDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.repository.AlbumsRepository; 7 | 8 | import java.util.List; 9 | 10 | public class AlbumsDataFetcher implements RxDataFetcher> { 11 | 12 | private final AlbumsRepository albumsRepository; 13 | 14 | public AlbumsDataFetcher(AlbumsRepository albumsRepository) { 15 | this.albumsRepository = albumsRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | Integer genre = env.getArgument("genre"); 21 | return albumsRepository.findAll(genre); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/CartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class CartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public CartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) { 18 | String curentUser = UserUtil.currentUser(env); 19 | return curentUser==null ? null:cartRepository.findCart(curentUser); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/CartItemAlbumDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Album; 6 | import workshop.model.CartItem; 7 | import workshop.repository.AlbumsRepository; 8 | 9 | public class CartItemAlbumDataFetcher implements RxDataFetcher { 10 | 11 | private final AlbumsRepository albumsRepository; 12 | 13 | public CartItemAlbumDataFetcher(AlbumsRepository albumsRepository) { 14 | this.albumsRepository = albumsRepository; 15 | } 16 | 17 | @Override 18 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 19 | CartItem cartItem = env.getSource(); 20 | return albumsRepository.findById(cartItem.getAlbumId(), false); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/CurrentUserDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetcher; 4 | import graphql.schema.DataFetchingEnvironment; 5 | 6 | public class CurrentUserDataFetcher implements DataFetcher { 7 | 8 | @Override 9 | public String get(DataFetchingEnvironment env) throws Exception { 10 | return UserUtil.currentUser(env); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/GenresDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Genre; 6 | import workshop.repository.GenresRepository; 7 | 8 | import java.util.List; 9 | 10 | public class GenresDataFetcher implements RxDataFetcher> { 11 | 12 | private final GenresRepository genresRepository; 13 | 14 | public GenresDataFetcher(GenresRepository genresRepository) { 15 | this.genresRepository = genresRepository; 16 | } 17 | 18 | @Override 19 | public Single> rxGet(DataFetchingEnvironment env) { 20 | return genresRepository.findAll(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/RemoveFromCartDataFetcher.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.reactivex.Single; 5 | import workshop.model.Cart; 6 | import workshop.repository.CartRepository; 7 | 8 | public class RemoveFromCartDataFetcher implements RxDataFetcher { 9 | 10 | private final CartRepository cartRepository; 11 | 12 | public RemoveFromCartDataFetcher(CartRepository cartRepository) { 13 | this.cartRepository = cartRepository; 14 | } 15 | 16 | @Override 17 | public Single rxGet(DataFetchingEnvironment env) throws Exception { 18 | String currentUser = UserUtil.currentUser(env); 19 | if (currentUser==null) { 20 | throw new NotLoggedInException(); 21 | } 22 | Integer albumId = env.getArgument("albumId"); 23 | return cartRepository.removeFromCart(currentUser, albumId).andThen(cartRepository.findCart(currentUser)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/java/workshop/gateway/UserUtil.java: -------------------------------------------------------------------------------- 1 | package workshop.gateway; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import io.vertx.ext.auth.User; 5 | import io.vertx.ext.web.RoutingContext; 6 | 7 | public class UserUtil { 8 | 9 | public static String currentUser(DataFetchingEnvironment env) { 10 | RoutingContext routingContext = env.getContext(); 11 | User user = routingContext.user(); 12 | return user!=null ? user.principal().getString("username"):null; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/resources/musicstore.graphql: -------------------------------------------------------------------------------- 1 | type Genre { 2 | id: Int! 3 | name: String! 4 | image: String 5 | } 6 | 7 | type Track { 8 | number: Int! 9 | name: String! 10 | } 11 | 12 | type Album { 13 | id: Int! 14 | name: String! 15 | genre: Genre! 16 | artist: String! 17 | image: String 18 | tracks: [Track!]! 19 | rating: Int 20 | reviews: [Review!] 21 | } 22 | 23 | type Review { 24 | name: String! 25 | rating: Int! 26 | comment: String 27 | } 28 | 29 | type CartItem { 30 | album: Album 31 | quantity: Int 32 | } 33 | 34 | type Cart { 35 | items: [CartItem!] 36 | } 37 | 38 | type Query { 39 | genres: [Genre!] 40 | albums(genre: Int): [Album!] 41 | album(id: Int!): Album 42 | cart: Cart 43 | currentUser: String 44 | } 45 | 46 | input ReviewInput { 47 | rating: Int! 48 | comment: String 49 | } 50 | 51 | type ReviewResult { 52 | rating: Int! 53 | reviews: [Review!]! 54 | } 55 | 56 | type Mutation { 57 | addToCart(albumId: Int!): Cart 58 | removeFromCart(albumId: Int!): Cart 59 | addReview(albumId: Int!, review: ReviewInput): ReviewResult 60 | } 61 | 62 | schema { 63 | query: Query 64 | mutation: Mutation 65 | } 66 | -------------------------------------------------------------------------------- /steps/step-7/src/solution/resources/passwordfile: -------------------------------------------------------------------------------- 1 | vladimir:vladimir 2 | thomas:thomas 3 | -------------------------------------------------------------------------------- /watch-doc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e -x 4 | 5 | while inotifywait -e close_write workshop.adoc; do ./build-doc.sh; done 6 | --------------------------------------------------------------------------------