├── README.md ├── gamification ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ ├── GamificationApplication.java │ │ │ └── gamification │ │ │ ├── client │ │ │ ├── MultiplicationResultAttemptClient.java │ │ │ ├── MultiplicationResultAttemptClientImpl.java │ │ │ ├── MultiplicationResultAttemptDeserializer.java │ │ │ └── dto │ │ │ │ └── MultiplicationResultAttempt.java │ │ │ ├── configuration │ │ │ ├── RabbitMQConfiguration.java │ │ │ ├── RestClientConfiguration.java │ │ │ └── WebConfiguration.java │ │ │ ├── controller │ │ │ ├── LeaderBoardController.java │ │ │ └── UserStatsController.java │ │ │ ├── domain │ │ │ ├── Badge.java │ │ │ ├── BadgeCard.java │ │ │ ├── GameStats.java │ │ │ ├── LeaderBoardRow.java │ │ │ └── ScoreCard.java │ │ │ ├── event │ │ │ ├── EventHandler.java │ │ │ └── MultiplicationSolvedEvent.java │ │ │ ├── repository │ │ │ ├── BadgeCardRepository.java │ │ │ └── ScoreCardRepository.java │ │ │ └── service │ │ │ ├── GameService.java │ │ │ ├── GameServiceImpl.java │ │ │ ├── LeaderBoardService.java │ │ │ └── LeaderBoardServiceImpl.java │ └── resources │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ └── java │ └── microservices │ └── book │ └── gamification │ ├── controller │ ├── LeaderBoardControllerTest.java │ └── UserStatsControllerTest.java │ ├── event │ └── EventHandlerTest.java │ └── service │ ├── GameServiceImplTest.java │ └── LeaderBoardServiceImplTest.java ├── gateway ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ └── gateway │ │ │ ├── GatewayApplication.java │ │ │ └── configuration │ │ │ ├── HystrixFallbackConfiguration.java │ │ │ ├── RibbonConfiguration.java │ │ │ └── WebConfiguration.java │ └── resources │ │ ├── application.yml │ │ └── bootstrap.yml │ └── test │ └── java │ └── microservices │ └── book │ └── gateway │ └── GatewayApplicationTests.java ├── resources └── logical_view_v8.png ├── service-registry ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ └── serviceregistry │ │ │ └── ServiceRegistryApplication.java │ └── resources │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ └── java │ └── microservices │ └── book │ └── serviceregistry │ └── ServiceRegistryApplicationTests.java ├── social-multiplication ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── microservices │ │ │ └── book │ │ │ ├── SocialMultiplicationApplication.java │ │ │ └── multiplication │ │ │ ├── configuration │ │ │ ├── RabbitMQConfiguration.java │ │ │ └── WebConfiguration.java │ │ │ ├── controller │ │ │ ├── MultiplicationController.java │ │ │ └── MultiplicationResultAttemptController.java │ │ │ ├── domain │ │ │ ├── Multiplication.java │ │ │ ├── MultiplicationResultAttempt.java │ │ │ └── User.java │ │ │ ├── event │ │ │ ├── EventDispatcher.java │ │ │ └── MultiplicationSolvedEvent.java │ │ │ ├── repository │ │ │ ├── MultiplicationRepository.java │ │ │ ├── MultiplicationResultAttemptRepository.java │ │ │ └── UserRepository.java │ │ │ └── service │ │ │ ├── MultiplicationService.java │ │ │ ├── MultiplicationServiceImpl.java │ │ │ ├── RandomGeneratorService.java │ │ │ └── RandomGeneratorServiceImpl.java │ └── resources │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ └── java │ └── microservices │ └── book │ └── multiplication │ ├── controller │ ├── MultiplicationControllerTest.java │ └── MultiplicationResultAttemptControllerTest.java │ └── service │ ├── MultiplicationServiceImplTest.java │ └── RandomGeneratorServiceImplTest.java └── ui ├── start.d ├── deploy.ini └── http.ini └── webapps └── ui ├── css ├── bootstrap-theme.css ├── bootstrap-theme.css.map ├── bootstrap-theme.min.css ├── bootstrap-theme.min.css.map ├── bootstrap.css ├── bootstrap.css.map ├── bootstrap.min.css └── bootstrap.min.css.map ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── gamification-client.js ├── index.html ├── js ├── bootstrap.js ├── bootstrap.min.js └── npm.js ├── multiplication-client.js └── styles.css /README.md: -------------------------------------------------------------------------------- 1 | # Learn Microservices with Spring Boot - v8 2 | 3 | This project contains the version 8 of the application that is developed under the scope of the book *Learn Microservices with Spring Boot*. You can get a copy of the book on [Amazon](http://amzn.to/2FSB2ME) or [Apress](http://www.apress.com/book/9781484231647). 4 | 5 | The book shows you how to evolve a simple Spring Boot application to become a full Microservices Architecture, using Spring Cloud Eureka, Ribbon, Zuul and Hystrix to implement Service Discovery, Load Balancing, the API Gateway pattern and a Circuit Breaker. Besides, you'll learn how to implement End-to-End tests with Cucumber, an Event-Driven system and the best practices when building Microservices. 6 | 7 | ## Idea 8 | 9 | These projects, included in [Microservices-Practical repositories](https://github.com/microservices-practical), illustrate how to start an application from scratch and then evolve it to become a full microservices environment. 10 | 11 | This version introduces Load Balancing with Ribbon, configured to detect services that are down through a `PingUrl` and combined with an `AvailabilityFilteringRule`. 12 | 13 | ## Contents 14 | 15 | The repository contains five folders, one for each component of the system: 16 | 17 | * **social-multiplication** is one of the backend services. It has a REST API to get and provide results to simple multiplications. When an attempt is sent, it triggers an *event*. 18 | * **gamification** is the second backend service. It provides a REST API to get game stats and reacts to the event sent by the multiplication service, updating the figures. 19 | * **service-registry** is the Eureka Server, which is contacted by backend services and the gateway. 20 | * **gateway** is the Routing Service, implemented with Zuul. It connects with Eureka for service discovery, and performs load balancing with Ribbon. 21 | * **ui** contains the static files of the frontend application. It's configured to be started with Jetty. 22 | 23 | ## How to execute the application 24 | 25 | Since in this version we don't have yet scripts to do it, we need to run the applications manually. These are the instructions: 26 | 27 | * **UI**. Download and install [Jetty](http://www.eclipse.org/jetty/download.html). From the command line, execute `java -jar [JETTY_HOME]/jetty-[VERSION]/start.jar` (replace the values between brackets accordingly). You also need Java. 28 | * **RabbitMQ**. Download and install [RabbitMQ](https://www.rabbitmq.com/download.html). We use it as Event bus. When you have installed it, you need to run the RabbitMQ server (as a service or as a process, whatever you prefer). 29 | * **Multiplication, Gamification, Service Registry and Gateway**. You need to start all these services using the command line. Navigate to each folder and execute for every one of them: `./mvnw spring-boot:run`. If you want to try load balancing you can execute more than one instance of the Multiplication or Gamification service. To do that, override the port number to avoid clashing: `./mvnw spring-boot:run -Drun.arguments="--server.port=A_FREE_PORT]"` 30 | 31 | ![Application - version 8](resources/logical_view_v8.png) 32 | 33 | ## Do you want to know more? 34 | 35 | If you want more details about the different application parts and how to get there from scratch, you can buy the book if you don't have it yet: *Learn Microservices with Spring Boot* on [Amazon](http://amzn.to/2FSB2ME) or [Apress](http://www.apress.com/book/9781484231647) 36 | -------------------------------------------------------------------------------- /gamification/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /gamification/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/gamification/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /gamification/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /gamification/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /gamification/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /gamification/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | gamification-v8 8 | 0.8.0-SNAPSHOT 9 | jar 10 | 11 | gamification-v8 12 | Social Multiplication App - Gamification (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-dependencies 33 | ${spring-cloud.version} 34 | pom 35 | import 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-amqp 49 | 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-starter-eureka 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-actuator 59 | 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.16.18 66 | provided 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-data-jpa 72 | 73 | 74 | 75 | org.springframework.cloud 76 | spring-cloud-starter-hystrix 77 | 78 | 79 | 80 | com.h2database 81 | h2 82 | runtime 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-starter-test 88 | test 89 | 90 | 91 | 92 | 93 | 94 | 95 | org.springframework.boot 96 | spring-boot-maven-plugin 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/GamificationApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | 8 | @EnableEurekaClient 9 | @EnableCircuitBreaker 10 | @SpringBootApplication 11 | public class GamificationApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(GamificationApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/MultiplicationResultAttemptClient.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client; 2 | 3 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 4 | 5 | /** 6 | * This interface allows us to connect to the Multiplication microservice. 7 | * Note that it's agnostic to the way of communication. 8 | */ 9 | public interface MultiplicationResultAttemptClient { 10 | 11 | MultiplicationResultAttempt retrieveMultiplicationResultAttemptbyId(final Long multiplicationId); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/MultiplicationResultAttemptClientImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client; 2 | 3 | import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; 4 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.client.RestTemplate; 9 | 10 | /** 11 | * This implementation of MultiplicationResultAttemptClient interface connects to 12 | * the Multiplication microservice via REST. 13 | */ 14 | @Component 15 | class MultiplicationResultAttemptClientImpl implements MultiplicationResultAttemptClient { 16 | 17 | private final RestTemplate restTemplate; 18 | private final String multiplicationHost; 19 | 20 | @Autowired 21 | public MultiplicationResultAttemptClientImpl(final RestTemplate restTemplate, 22 | @Value("${multiplicationHost}") final String multiplicationHost) { 23 | this.restTemplate = restTemplate; 24 | this.multiplicationHost = multiplicationHost; 25 | } 26 | 27 | @HystrixCommand(fallbackMethod = "defaultResult") 28 | @Override 29 | public MultiplicationResultAttempt retrieveMultiplicationResultAttemptbyId(final Long multiplicationResultAttemptId) { 30 | return restTemplate.getForObject( 31 | multiplicationHost + "/results/" + multiplicationResultAttemptId, 32 | MultiplicationResultAttempt.class); 33 | } 34 | 35 | private MultiplicationResultAttempt defaultResult(final Long multiplicationResultAttemptId) { 36 | return new MultiplicationResultAttempt("fakeAlias", 37 | 10, 10, 100, true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/MultiplicationResultAttemptDeserializer.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.core.ObjectCodec; 6 | import com.fasterxml.jackson.databind.DeserializationContext; 7 | import com.fasterxml.jackson.databind.JsonDeserializer; 8 | import com.fasterxml.jackson.databind.JsonNode; 9 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 10 | 11 | import java.io.IOException; 12 | 13 | /** 14 | * Deserializes an attempt coming from the Multiplication microservice 15 | * into the Gamification's representation of an attempt. 16 | */ 17 | public class MultiplicationResultAttemptDeserializer 18 | extends JsonDeserializer { 19 | 20 | @Override 21 | public MultiplicationResultAttempt deserialize(JsonParser jsonParser, 22 | DeserializationContext deserializationContext) 23 | throws IOException, JsonProcessingException { 24 | ObjectCodec oc = jsonParser.getCodec(); 25 | JsonNode node = oc.readTree(jsonParser); 26 | return new MultiplicationResultAttempt(node.get("user").get("alias").asText(), 27 | node.get("multiplication").get("factorA").asInt(), 28 | node.get("multiplication").get("factorB").asInt(), 29 | node.get("resultAttempt").asInt(), 30 | node.get("correct").asBoolean()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/client/dto/MultiplicationResultAttempt.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.client.dto; 2 | 3 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.ToString; 8 | import microservices.book.gamification.client.MultiplicationResultAttemptDeserializer; 9 | 10 | /** 11 | * Identifies the attempt from a user to solve a multiplication. 12 | */ 13 | @RequiredArgsConstructor 14 | @Getter 15 | @ToString 16 | @EqualsAndHashCode 17 | @JsonDeserialize(using = MultiplicationResultAttemptDeserializer.class) 18 | public final class MultiplicationResultAttempt { 19 | 20 | private final String userAlias; 21 | 22 | private final int multiplicationFactorA; 23 | private final int multiplicationFactorB; 24 | private final int resultAttempt; 25 | 26 | private final boolean correct; 27 | 28 | // Empty constructor for JSON/JPA 29 | MultiplicationResultAttempt() { 30 | userAlias = null; 31 | multiplicationFactorA = -1; 32 | multiplicationFactorB = -1; 33 | resultAttempt = -1; 34 | correct = false; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/configuration/RabbitMQConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.configuration; 2 | 3 | import org.springframework.amqp.core.Binding; 4 | import org.springframework.amqp.core.BindingBuilder; 5 | import org.springframework.amqp.core.Queue; 6 | import org.springframework.amqp.core.TopicExchange; 7 | import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer; 8 | import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.messaging.converter.MappingJackson2MessageConverter; 13 | import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory; 14 | 15 | /** 16 | * Configures RabbitMQ to use events in our application. 17 | */ 18 | @Configuration 19 | public class RabbitMQConfiguration implements RabbitListenerConfigurer { 20 | 21 | @Bean 22 | public TopicExchange multiplicationExchange(@Value("${multiplication.exchange}") final String exchangeName) { 23 | return new TopicExchange(exchangeName); 24 | } 25 | 26 | @Bean 27 | public Queue gamificationMultiplicationQueue(@Value("${multiplication.queue}") final String queueName) { 28 | return new Queue(queueName, true); 29 | } 30 | 31 | @Bean 32 | Binding binding(final Queue queue, final TopicExchange exchange, 33 | @Value("${multiplication.anything.routing-key}") final String routingKey) { 34 | return BindingBuilder.bind(queue).to(exchange).with(routingKey); 35 | } 36 | 37 | @Bean 38 | public MappingJackson2MessageConverter consumerJackson2MessageConverter() { 39 | return new MappingJackson2MessageConverter(); 40 | } 41 | 42 | @Bean 43 | public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() { 44 | DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); 45 | factory.setMessageConverter(consumerJackson2MessageConverter()); 46 | return factory; 47 | } 48 | 49 | @Override 50 | public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) { 51 | registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/configuration/RestClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.configuration; 2 | 3 | import org.springframework.boot.web.client.RestTemplateBuilder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.client.RestTemplate; 7 | 8 | /** 9 | * Configures the REST client in our application 10 | */ 11 | @Configuration 12 | public class RestClientConfiguration { 13 | 14 | @Bean 15 | public RestTemplate restTemplate(RestTemplateBuilder builder) { 16 | return builder.build(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | public class WebConfiguration extends WebMvcConfigurerAdapter { 14 | 15 | /** 16 | * Enables Cross-Origin Resource Sharing (CORS) 17 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 18 | * @param registry 19 | */ 20 | @Override 21 | public void addCorsMappings(final CorsRegistry registry) { 22 | registry.addMapping("/**"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/controller/LeaderBoardController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.service.LeaderBoardService; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RestController; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * This class implements a REST API for the Gamification LeaderBoard service. 13 | */ 14 | @RestController 15 | @RequestMapping("/leaders") 16 | class LeaderBoardController { 17 | 18 | private final LeaderBoardService leaderBoardService; 19 | 20 | public LeaderBoardController(final LeaderBoardService leaderBoardService) { 21 | this.leaderBoardService = leaderBoardService; 22 | } 23 | 24 | @GetMapping 25 | public List getLeaderBoard() { 26 | return leaderBoardService.getCurrentLeaderBoard(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/controller/UserStatsController.java: -------------------------------------------------------------------------------- 1 | 2 | package microservices.book.gamification.controller; 3 | 4 | import microservices.book.gamification.domain.GameStats; 5 | import microservices.book.gamification.service.GameService; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | * This class implements a REST API for the Gamification User Statistics service. 13 | */ 14 | @RestController 15 | @RequestMapping("/stats") 16 | class UserStatsController { 17 | 18 | private final GameService gameService; 19 | 20 | public UserStatsController(final GameService gameService) { 21 | this.gameService = gameService; 22 | } 23 | 24 | @GetMapping 25 | public GameStats getStatsForUser(@RequestParam("userId") final Long userId) { 26 | return gameService.retrieveStatsForUser(userId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/Badge.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | /** 4 | * Enumeration with the different types of Badges that a user can win. 5 | */ 6 | public enum Badge { 7 | 8 | // Badges depending on score 9 | BRONZE_MULTIPLICATOR, 10 | SILVER_MULTIPLICATOR, 11 | GOLD_MULTIPLICATOR, 12 | 13 | // Other badges won for different conditions 14 | FIRST_ATTEMPT, 15 | FIRST_WON, 16 | LUCKY_NUMBER 17 | 18 | } 19 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/BadgeCard.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * This class links a Badge to a User. Contains also a timestamp with the moment in which the user got it. 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @ToString 19 | @EqualsAndHashCode 20 | @Entity 21 | public final class BadgeCard { 22 | 23 | @Id 24 | @GeneratedValue 25 | @Column(name = "BADGE_ID") 26 | private final Long badgeId; 27 | 28 | private final Long userId; 29 | private final long badgeTimestamp; 30 | private final Badge badge; 31 | 32 | // Empty constructor for JSON / JPA 33 | public BadgeCard() { 34 | this(null, null, 0, null); 35 | } 36 | 37 | public BadgeCard(final Long userId, final Badge badge) { 38 | this(null, userId, System.currentTimeMillis(), badge); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/GameStats.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | /** 13 | * This object contains the result of one or many iterations of the game. 14 | * It may contain any combination of {@link ScoreCard} objects and {@link BadgeCard} objects. 15 | * 16 | * It can be used as a delta (as a single game iteration) or to represent the total amount of score / badges. 17 | */ 18 | @RequiredArgsConstructor 19 | @Getter 20 | @ToString 21 | @EqualsAndHashCode 22 | public final class GameStats { 23 | 24 | private final Long userId; 25 | private final int score; 26 | private final List badges; 27 | 28 | // Empty constructor for JSON / JPA 29 | public GameStats() { 30 | this.userId = 0L; 31 | this.score = 0; 32 | this.badges = new ArrayList<>(); 33 | } 34 | 35 | /** 36 | * Factory method to build an empty instance (zero points and no badges) 37 | * @param userId the user's id 38 | * @return a {@link GameStats} object with zero score and no badges 39 | */ 40 | public static GameStats emptyStats(final Long userId) { 41 | return new GameStats(userId, 0, Collections.emptyList()); 42 | } 43 | 44 | /** 45 | * @return an unmodifiable view of the badge cards list 46 | */ 47 | public List getBadges() { 48 | return Collections.unmodifiableList(badges); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/LeaderBoardRow.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | /** 9 | * Represents a line in our Leaderboard: it links a user to a total score. 10 | */ 11 | @RequiredArgsConstructor 12 | @Getter 13 | @ToString 14 | @EqualsAndHashCode 15 | public final class LeaderBoardRow { 16 | 17 | private final Long userId; 18 | private final Long totalScore; 19 | 20 | // Empty constructor for JSON / JPA 21 | public LeaderBoardRow() { 22 | this(0L, 0L); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/domain/ScoreCard.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * This class represents the Score linked to an attempt in the game, 15 | * with an associated user and the timestamp in which the score 16 | * is registered. 17 | */ 18 | @RequiredArgsConstructor 19 | @Getter 20 | @ToString 21 | @EqualsAndHashCode 22 | @Entity 23 | public final class ScoreCard { 24 | 25 | // The default score assigned to this card, if not specified. 26 | public static final int DEFAULT_SCORE = 10; 27 | 28 | @Id 29 | @GeneratedValue 30 | @Column(name = "CARD_ID") 31 | private final Long cardId; 32 | 33 | @Column(name = "USER_ID") 34 | private final Long userId; 35 | 36 | @Column(name = "ATTEMPT_ID") 37 | private final Long attemptId; 38 | 39 | @Column(name = "SCORE_TS") 40 | private final long scoreTimestamp; 41 | 42 | @Column(name = "SCORE") 43 | private final int score; 44 | 45 | // Empty constructor for JSON / JPA 46 | public ScoreCard() { 47 | this(null, null, null, 0, 0); 48 | } 49 | 50 | public ScoreCard(final Long userId, final Long attemptId) { 51 | this(null, userId, attemptId, System.currentTimeMillis(), DEFAULT_SCORE); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/event/EventHandler.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.event; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.gamification.service.GameService; 5 | import org.springframework.amqp.AmqpRejectAndDontRequeueException; 6 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * This class receives the events and triggers the associated 11 | * business logic. 12 | */ 13 | @Slf4j 14 | @Component 15 | class EventHandler { 16 | 17 | private GameService gameService; 18 | 19 | EventHandler(final GameService gameService) { 20 | this.gameService = gameService; 21 | } 22 | 23 | @RabbitListener(queues = "${multiplication.queue}") 24 | void handleMultiplicationSolved(final MultiplicationSolvedEvent event) { 25 | log.info("Multiplication Solved Event received: {}", event.getMultiplicationResultAttemptId()); 26 | try { 27 | gameService.newAttemptForUser(event.getUserId(), 28 | event.getMultiplicationResultAttemptId(), 29 | event.isCorrect()); 30 | } catch (final Exception e) { 31 | log.error("Error when trying to process MultiplicationSolvedEvent", e); 32 | // Avoids the event to be re-queued and reprocessed. 33 | throw new AmqpRejectAndDontRequeueException(e); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/event/MultiplicationSolvedEvent.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.event; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Event received when a multiplication has been solved in the system. 12 | * Provides some context information about the multiplication. 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | @ToString 17 | @EqualsAndHashCode 18 | class MultiplicationSolvedEvent implements Serializable { 19 | 20 | private final Long multiplicationResultAttemptId; 21 | private final Long userId; 22 | private final boolean correct; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/repository/BadgeCardRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.repository; 2 | 3 | import microservices.book.gamification.domain.BadgeCard; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Handles data operations with BadgeCards 10 | */ 11 | public interface BadgeCardRepository extends CrudRepository { 12 | 13 | /** 14 | * Retrieves all BadgeCards for a given user. 15 | * @param userId the id of the user to look for BadgeCards 16 | * @return the list of BadgeCards, sorted by most recent. 17 | */ 18 | List findByUserIdOrderByBadgeTimestampDesc(final Long userId); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/repository/ScoreCardRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.repository; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.domain.ScoreCard; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.repository.CrudRepository; 7 | import org.springframework.data.repository.query.Param; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Handles CRUD operations with ScoreCards 13 | */ 14 | public interface ScoreCardRepository extends CrudRepository { 15 | 16 | /** 17 | * Gets the total score for a given user, being the sum of the scores of all his ScoreCards. 18 | * @param userId the id of the user for which the total score should be retrieved 19 | * @return the total score for the given user 20 | */ 21 | @Query("SELECT SUM(s.score) FROM microservices.book.gamification.domain.ScoreCard s WHERE s.userId = :userId GROUP BY s.userId") 22 | int getTotalScoreForUser(@Param("userId") final Long userId); 23 | 24 | /** 25 | * Retrieves a list of {@link LeaderBoardRow}s representing the Leader Board of users and their total score. 26 | * @return the leader board, sorted by highest score first. 27 | */ 28 | @Query("SELECT NEW microservices.book.gamification.domain.LeaderBoardRow(s.userId, SUM(s.score)) " + 29 | "FROM microservices.book.gamification.domain.ScoreCard s " + 30 | "GROUP BY s.userId ORDER BY SUM(s.score) DESC") 31 | List findFirst10(); 32 | 33 | /** 34 | * Retrieves all the ScoreCards for a given user, identified by his user id. 35 | * @param userId the id of the user 36 | * @return a list containing all the ScoreCards for the given user, sorted by most recent. 37 | */ 38 | List findByUserIdOrderByScoreTimestampDesc(final Long userId); 39 | } 40 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/GameService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.GameStats; 4 | 5 | /** 6 | * This service includes the main logic for gamifying the system. 7 | */ 8 | public interface GameService { 9 | 10 | /** 11 | * Process a new attempt from a given user. 12 | * 13 | * @param userId the user's unique id 14 | * @param attemptId the attempt id, can be used to retrieve extra data if needed 15 | * @param correct indicates if the attempt was correct 16 | * 17 | * @return a {@link GameStats} object containing the new score and badge cards obtained 18 | */ 19 | GameStats newAttemptForUser(Long userId, Long attemptId, boolean correct); 20 | 21 | /** 22 | * Gets the game statistics for a given user 23 | * @param userId the user 24 | * @return the total statistics for that user 25 | */ 26 | GameStats retrieveStatsForUser(Long userId); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/GameServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.gamification.client.MultiplicationResultAttemptClient; 5 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 6 | import microservices.book.gamification.domain.*; 7 | import microservices.book.gamification.repository.BadgeCardRepository; 8 | import microservices.book.gamification.repository.ScoreCardRepository; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.stream.Collectors; 15 | 16 | /** 17 | * @author moises.macero 18 | */ 19 | @Service 20 | @Slf4j 21 | class GameServiceImpl implements GameService { 22 | 23 | public static final int LUCKY_NUMBER = 42; 24 | 25 | private ScoreCardRepository scoreCardRepository; 26 | private BadgeCardRepository badgeCardRepository; 27 | private MultiplicationResultAttemptClient attemptClient; 28 | 29 | GameServiceImpl(ScoreCardRepository scoreCardRepository, 30 | BadgeCardRepository badgeCardRepository, 31 | MultiplicationResultAttemptClient attemptClient) { 32 | this.scoreCardRepository = scoreCardRepository; 33 | this.badgeCardRepository = badgeCardRepository; 34 | this.attemptClient = attemptClient; 35 | } 36 | 37 | @Override 38 | public GameStats newAttemptForUser(final Long userId, 39 | final Long attemptId, 40 | final boolean correct) { 41 | // For the first version we'll give points only if it's correct 42 | if(correct) { 43 | ScoreCard scoreCard = new ScoreCard(userId, attemptId); 44 | scoreCardRepository.save(scoreCard); 45 | log.info("User with id {} scored {} points for attempt id {}", 46 | userId, scoreCard.getScore(), attemptId); 47 | List badgeCards = processForBadges(userId, attemptId); 48 | return new GameStats(userId, scoreCard.getScore(), 49 | badgeCards.stream().map(BadgeCard::getBadge) 50 | .collect(Collectors.toList())); 51 | } 52 | return GameStats.emptyStats(userId); 53 | } 54 | 55 | /** 56 | * Checks the total score and the different score cards obtained 57 | * to give new badges in case their conditions are met. 58 | */ 59 | private List processForBadges(final Long userId, 60 | final Long attemptId) { 61 | List badgeCards = new ArrayList<>(); 62 | 63 | int totalScore = scoreCardRepository.getTotalScoreForUser(userId); 64 | log.info("New score for user {} is {}", userId, totalScore); 65 | 66 | List scoreCardList = scoreCardRepository 67 | .findByUserIdOrderByScoreTimestampDesc(userId); 68 | List badgeCardList = badgeCardRepository 69 | .findByUserIdOrderByBadgeTimestampDesc(userId); 70 | 71 | // Badges depending on score 72 | checkAndGiveBadgeBasedOnScore(badgeCardList, 73 | Badge.BRONZE_MULTIPLICATOR, totalScore, 100, userId) 74 | .ifPresent(badgeCards::add); 75 | checkAndGiveBadgeBasedOnScore(badgeCardList, 76 | Badge.SILVER_MULTIPLICATOR, totalScore, 500, userId) 77 | .ifPresent(badgeCards::add); 78 | checkAndGiveBadgeBasedOnScore(badgeCardList, 79 | Badge.GOLD_MULTIPLICATOR, totalScore, 999, userId) 80 | .ifPresent(badgeCards::add); 81 | 82 | // First won badge 83 | if(scoreCardList.size() == 1 && 84 | !containsBadge(badgeCardList, Badge.FIRST_WON)) { 85 | BadgeCard firstWonBadge = giveBadgeToUser(Badge.FIRST_WON, userId); 86 | badgeCards.add(firstWonBadge); 87 | } 88 | 89 | // Lucky number badge 90 | MultiplicationResultAttempt attempt = attemptClient 91 | .retrieveMultiplicationResultAttemptbyId(attemptId); 92 | if(!containsBadge(badgeCardList, Badge.LUCKY_NUMBER) && 93 | (LUCKY_NUMBER == attempt.getMultiplicationFactorA() || 94 | LUCKY_NUMBER == attempt.getMultiplicationFactorB())) { 95 | BadgeCard luckyNumberBadge = giveBadgeToUser( 96 | Badge.LUCKY_NUMBER, userId); 97 | badgeCards.add(luckyNumberBadge); 98 | } 99 | 100 | return badgeCards; 101 | } 102 | 103 | @Override 104 | public GameStats retrieveStatsForUser(final Long userId) { 105 | int score = scoreCardRepository.getTotalScoreForUser(userId); 106 | List badgeCards = badgeCardRepository 107 | .findByUserIdOrderByBadgeTimestampDesc(userId); 108 | return new GameStats(userId, score, badgeCards.stream() 109 | .map(BadgeCard::getBadge).collect(Collectors.toList())); 110 | } 111 | 112 | /** 113 | * Convenience method to check the current score against 114 | * the different thresholds to gain badges. 115 | * It also assigns badge to user if the conditions are met. 116 | */ 117 | private Optional checkAndGiveBadgeBasedOnScore( 118 | final List badgeCards, final Badge badge, 119 | final int score, final int scoreThreshold, final Long userId) { 120 | if(score >= scoreThreshold && !containsBadge(badgeCards, badge)) { 121 | return Optional.of(giveBadgeToUser(badge, userId)); 122 | } 123 | return Optional.empty(); 124 | } 125 | 126 | /** 127 | * Checks if the passed list of badges includes the one being checked 128 | */ 129 | private boolean containsBadge(final List badgeCards, 130 | final Badge badge) { 131 | return badgeCards.stream().anyMatch(b -> b.getBadge().equals(badge)); 132 | } 133 | 134 | /** 135 | * Assigns a new badge to the given user 136 | */ 137 | private BadgeCard giveBadgeToUser(final Badge badge, final Long userId) { 138 | BadgeCard badgeCard = new BadgeCard(userId, badge); 139 | badgeCardRepository.save(badgeCard); 140 | log.info("User with id {} won a new badge: {}", userId, badge); 141 | return badgeCard; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/LeaderBoardService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Provides methods to access the LeaderBoard with users and scores. 9 | */ 10 | public interface LeaderBoardService { 11 | 12 | /** 13 | * Retrieves the current leader board with the top score users 14 | * @return the users with the highest score 15 | */ 16 | List getCurrentLeaderBoard(); 17 | } 18 | -------------------------------------------------------------------------------- /gamification/src/main/java/microservices/book/gamification/service/LeaderBoardServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.repository.ScoreCardRepository; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | 9 | @Service 10 | class LeaderBoardServiceImpl implements LeaderBoardService { 11 | 12 | private ScoreCardRepository scoreCardRepository; 13 | 14 | LeaderBoardServiceImpl(ScoreCardRepository scoreCardRepository) { 15 | this.scoreCardRepository = scoreCardRepository; 16 | } 17 | 18 | @Override 19 | public List getCurrentLeaderBoard() { 20 | return scoreCardRepository.findFirst10(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gamification/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8081 2 | 3 | # Gives us access to the H2 database web console 4 | spring.h2.console.enabled=true 5 | # Generates the database *only* if it's not there yet 6 | spring.jpa.hibernate.ddl-auto=update 7 | # Creates the database in a file 8 | spring.datasource.url=jdbc:h2:file:~/gamification;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE 9 | # For educational purposes we will show the SQL in console 10 | spring.jpa.properties.hibernate.show_sql=true 11 | 12 | ## RabbitMQ configuration 13 | multiplication.exchange=multiplication_exchange 14 | multiplication.solved.key=multiplication.solved 15 | multiplication.queue=gamification_multiplication_queue 16 | multiplication.anything.routing-key=multiplication.* 17 | 18 | # REST client settings 19 | multiplicationHost=http://localhost:8000/api 20 | 21 | # Service Discovery configuration 22 | eureka.client.service-url.default-zone=http://localhost:8761/eureka/ -------------------------------------------------------------------------------- /gamification/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=gamification -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/LeaderBoardControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.gamification.domain.LeaderBoardRow; 5 | import microservices.book.gamification.service.LeaderBoardService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 11 | import org.springframework.boot.test.json.JacksonTester; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.mock.web.MockHttpServletResponse; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Collections; 21 | import java.util.List; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | import static org.mockito.BDDMockito.given; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 26 | 27 | /** 28 | * @author moises.macero 29 | */ 30 | @RunWith(SpringRunner.class) 31 | @WebMvcTest(LeaderBoardController.class) 32 | public class LeaderBoardControllerTest { 33 | 34 | @MockBean 35 | private LeaderBoardService leaderBoardService; 36 | 37 | @Autowired 38 | private MockMvc mvc; 39 | 40 | private JacksonTester> json; 41 | 42 | @Before 43 | public void setup() { 44 | JacksonTester.initFields(this, new ObjectMapper()); 45 | } 46 | 47 | @Test 48 | public void getLeaderBoardTest() throws Exception{ 49 | // given 50 | LeaderBoardRow leaderBoardRow1 = new LeaderBoardRow(1L, 500L); 51 | LeaderBoardRow leaderBoardRow2 = new LeaderBoardRow(2L, 400L); 52 | List leaderBoard = new ArrayList<>(); 53 | Collections.addAll(leaderBoard, leaderBoardRow1, leaderBoardRow2); 54 | given(leaderBoardService.getCurrentLeaderBoard()).willReturn(leaderBoard); 55 | 56 | // when 57 | MockHttpServletResponse response = mvc.perform( 58 | get("/leaders") 59 | .accept(MediaType.APPLICATION_JSON)) 60 | .andReturn().getResponse(); 61 | 62 | // then 63 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 64 | assertThat(response.getContentAsString()) 65 | .isEqualTo(json.write(leaderBoard).getJson()); 66 | } 67 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/controller/UserStatsControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.gamification.domain.Badge; 5 | import microservices.book.gamification.domain.GameStats; 6 | import microservices.book.gamification.service.GameService; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 12 | import org.springframework.boot.test.json.JacksonTester; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.mock.web.MockHttpServletResponse; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | import java.util.Collections; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | import static org.mockito.BDDMockito.given; 24 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 25 | 26 | /** 27 | * @author moises.macero 28 | */ 29 | @RunWith(SpringRunner.class) 30 | @WebMvcTest(UserStatsController.class) 31 | public class UserStatsControllerTest { 32 | 33 | @MockBean 34 | private GameService gameService; 35 | 36 | @Autowired 37 | private MockMvc mvc; 38 | 39 | private JacksonTester json; 40 | 41 | @Before 42 | public void setup() { 43 | JacksonTester.initFields(this, new ObjectMapper()); 44 | } 45 | 46 | @Test 47 | public void getUserStatsTest() throws Exception{ 48 | // given 49 | GameStats gameStats = new GameStats(1L, 2000, Collections.singletonList(Badge.GOLD_MULTIPLICATOR)); 50 | given(gameService.retrieveStatsForUser(1L)).willReturn(gameStats); 51 | 52 | // when 53 | MockHttpServletResponse response = mvc.perform( 54 | get("/stats?userId=1") 55 | .accept(MediaType.APPLICATION_JSON)) 56 | .andReturn().getResponse(); 57 | 58 | // then 59 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 60 | assertThat(response.getContentAsString()) 61 | .isEqualTo(json.write(gameStats).getJson()); 62 | } 63 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/event/EventHandlerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.event; 2 | 3 | import microservices.book.gamification.domain.GameStats; 4 | import microservices.book.gamification.service.GameService; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import static org.mockito.BDDMockito.given; 11 | import static org.mockito.Mockito.verify; 12 | 13 | /** 14 | * @author moises.macero 15 | */ 16 | public class EventHandlerTest { 17 | 18 | private EventHandler eventHandler; 19 | 20 | @Mock 21 | private GameService gameService; 22 | 23 | @Before 24 | public void setUp() { 25 | MockitoAnnotations.initMocks(this); 26 | eventHandler = new EventHandler(gameService); 27 | } 28 | 29 | @Test 30 | public void multiplicationAttemptReceivedTest() { 31 | // given 32 | Long userId = 1L; 33 | Long attemptId = 8L; 34 | boolean correct = true; 35 | GameStats gameStatsExpected = new GameStats(); 36 | MultiplicationSolvedEvent event = new MultiplicationSolvedEvent(attemptId, userId, correct); 37 | given(gameService.newAttemptForUser(userId, attemptId, correct)).willReturn(gameStatsExpected); 38 | 39 | // when 40 | eventHandler.handleMultiplicationSolved(event); 41 | 42 | // then 43 | verify(gameService).newAttemptForUser(userId, attemptId, correct); 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/service/GameServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.client.MultiplicationResultAttemptClient; 4 | import microservices.book.gamification.client.dto.MultiplicationResultAttempt; 5 | import microservices.book.gamification.domain.*; 6 | import microservices.book.gamification.repository.BadgeCardRepository; 7 | import microservices.book.gamification.repository.ScoreCardRepository; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | import org.mockito.MockitoAnnotations; 12 | 13 | import java.util.Collections; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.IntStream; 17 | 18 | import static org.assertj.core.api.Assertions.assertThat; 19 | import static org.mockito.BDDMockito.given; 20 | import static org.mockito.Matchers.anyLong; 21 | 22 | /** 23 | * @author moises.macero 24 | */ 25 | public class GameServiceImplTest { 26 | 27 | private GameServiceImpl gameService; 28 | 29 | @Mock 30 | private ScoreCardRepository scoreCardRepository; 31 | 32 | @Mock 33 | private BadgeCardRepository badgeCardRepository; 34 | 35 | @Mock 36 | private MultiplicationResultAttemptClient multiplicationClient; 37 | 38 | @Before 39 | public void setUp() { 40 | // With this call to initMocks we tell Mockito to process the annotations 41 | MockitoAnnotations.initMocks(this); 42 | gameService = new GameServiceImpl(scoreCardRepository, badgeCardRepository, multiplicationClient); 43 | 44 | // Common given - attempt does not contain a lucky number by default 45 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 46 | "john_doe", 20, 70, 1400, true); 47 | given(multiplicationClient.retrieveMultiplicationResultAttemptbyId(anyLong())) 48 | .willReturn(attempt); 49 | } 50 | 51 | @Test 52 | public void processFirstCorrectAttemptTest() { 53 | // given 54 | Long userId = 1L; 55 | Long attemptId = 8L; 56 | int totalScore = 10; 57 | ScoreCard scoreCard = new ScoreCard(userId, attemptId); 58 | given(scoreCardRepository.getTotalScoreForUser(userId)) 59 | .willReturn(totalScore); 60 | // this repository will return the just-won score card 61 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 62 | .willReturn(Collections.singletonList(scoreCard)); 63 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 64 | .willReturn(Collections.emptyList()); 65 | 66 | 67 | // when 68 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, true); 69 | 70 | // assert - should score one card and win the badge FIRST_WON 71 | assertThat(iteration.getScore()).isEqualTo(scoreCard.getScore()); 72 | assertThat(iteration.getBadges()).containsOnly(Badge.FIRST_WON); 73 | } 74 | 75 | @Test 76 | public void processCorrectAttemptForScoreBadgeTest() { 77 | // given 78 | Long userId = 1L; 79 | Long attemptId = 29L; 80 | int totalScore = 100; 81 | BadgeCard firstWonBadge = new BadgeCard(userId, Badge.FIRST_WON); 82 | given(scoreCardRepository.getTotalScoreForUser(userId)) 83 | .willReturn(totalScore); 84 | // this repository will return the just-won score card 85 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 86 | .willReturn(createNScoreCards(10, userId)); 87 | // the first won badge is already there 88 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 89 | .willReturn(Collections.singletonList(firstWonBadge)); 90 | 91 | 92 | // when 93 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, true); 94 | 95 | // assert - should score one card and win the badge BRONZE 96 | assertThat(iteration.getScore()).isEqualTo(ScoreCard.DEFAULT_SCORE); 97 | assertThat(iteration.getBadges()).containsOnly(Badge.BRONZE_MULTIPLICATOR); 98 | } 99 | 100 | @Test 101 | public void processCorrectAttemptForLuckyNumberBadgeTest() { 102 | // given 103 | Long userId = 1L; 104 | Long attemptId = 29L; 105 | int totalScore = 10; 106 | BadgeCard firstWonBadge = new BadgeCard(userId, Badge.FIRST_WON); 107 | given(scoreCardRepository.getTotalScoreForUser(userId)) 108 | .willReturn(totalScore); 109 | // this repository will return the just-won score card 110 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 111 | .willReturn(createNScoreCards(1, userId)); 112 | // the first won badge is already there 113 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 114 | .willReturn(Collections.singletonList(firstWonBadge)); 115 | // the attempt includes the lucky number 116 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 117 | "john_doe", 42, 10, 420, true); 118 | given(multiplicationClient.retrieveMultiplicationResultAttemptbyId(attemptId)) 119 | .willReturn(attempt); 120 | 121 | // when 122 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, true); 123 | 124 | // assert - should score one card and win the badge LUCKY NUMBER 125 | assertThat(iteration.getScore()).isEqualTo(ScoreCard.DEFAULT_SCORE); 126 | assertThat(iteration.getBadges()).containsOnly(Badge.LUCKY_NUMBER); 127 | } 128 | 129 | @Test 130 | public void processWrongAttemptTest() { 131 | // given 132 | Long userId = 1L; 133 | Long attemptId = 8L; 134 | int totalScore = 10; 135 | ScoreCard scoreCard = new ScoreCard(userId, attemptId); 136 | given(scoreCardRepository.getTotalScoreForUser(userId)) 137 | .willReturn(totalScore); 138 | // this repository will return the just-won score card 139 | given(scoreCardRepository.findByUserIdOrderByScoreTimestampDesc(userId)) 140 | .willReturn(Collections.singletonList(scoreCard)); 141 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 142 | .willReturn(Collections.emptyList()); 143 | 144 | 145 | // when 146 | GameStats iteration = gameService.newAttemptForUser(userId, attemptId, false); 147 | 148 | // assert - shouldn't score anything 149 | assertThat(iteration.getScore()).isEqualTo(0); 150 | assertThat(iteration.getBadges()).isEmpty(); 151 | } 152 | 153 | @Test 154 | public void retrieveStatsForUserTest() { 155 | // given 156 | Long userId = 1L; 157 | int totalScore = 1000; 158 | BadgeCard badgeCard = new BadgeCard(userId, Badge.SILVER_MULTIPLICATOR); 159 | given(scoreCardRepository.getTotalScoreForUser(userId)) 160 | .willReturn(totalScore); 161 | given(badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId)) 162 | .willReturn(Collections.singletonList(badgeCard)); 163 | 164 | // when 165 | GameStats stats = gameService.retrieveStatsForUser(userId); 166 | 167 | // assert - should score one card and win the badge FIRST_WON 168 | assertThat(stats.getScore()).isEqualTo(totalScore); 169 | assertThat(stats.getBadges()).containsOnly(Badge.SILVER_MULTIPLICATOR); 170 | } 171 | 172 | private List createNScoreCards(int n, Long userId) { 173 | return IntStream.range(0, n) 174 | .mapToObj(i -> new ScoreCard(userId, (long)i)) 175 | .collect(Collectors.toList()); 176 | } 177 | } -------------------------------------------------------------------------------- /gamification/src/test/java/microservices/book/gamification/service/LeaderBoardServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gamification.service; 2 | 3 | import microservices.book.gamification.domain.LeaderBoardRow; 4 | import microservices.book.gamification.repository.ScoreCardRepository; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | import org.mockito.MockitoAnnotations; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.mockito.BDDMockito.given; 15 | 16 | /** 17 | * @author moises.macero 18 | */ 19 | public class LeaderBoardServiceImplTest { 20 | 21 | private LeaderBoardServiceImpl leaderBoardService; 22 | 23 | @Mock 24 | private ScoreCardRepository scoreCardRepository; 25 | 26 | @Before 27 | public void setUp() { 28 | MockitoAnnotations.initMocks(this); 29 | leaderBoardService = new LeaderBoardServiceImpl(scoreCardRepository); 30 | } 31 | 32 | @Test 33 | public void retrieveLeaderBoardTest() { 34 | // given 35 | Long userId = 1L; 36 | LeaderBoardRow leaderRow1 = new LeaderBoardRow(userId, 300L); 37 | List expectedLeaderBoard = Collections.singletonList(leaderRow1); 38 | given(scoreCardRepository.findFirst10()).willReturn(expectedLeaderBoard); 39 | 40 | // when 41 | List leaderBoard = leaderBoardService.getCurrentLeaderBoard(); 42 | 43 | // then 44 | assertThat(leaderBoard).isEqualTo(expectedLeaderBoard); 45 | } 46 | } -------------------------------------------------------------------------------- /gateway/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /gateway/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/gateway/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /gateway/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /gateway/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /gateway/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | gateway-v8 8 | 0.8.0-SNAPSHOT 9 | jar 10 | 11 | gateway-v8 12 | Multiplication App - Gateway (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-zuul 32 | 33 | 34 | 35 | org.springframework.cloud 36 | spring-cloud-starter-eureka 37 | 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-starter-ribbon 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-actuator 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.springframework.cloud 60 | spring-cloud-dependencies 61 | ${spring-cloud.version} 62 | pom 63 | import 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-maven-plugin 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/GatewayApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway; 2 | 3 | import microservices.book.gateway.configuration.RibbonConfiguration; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | import org.springframework.cloud.netflix.ribbon.RibbonClients; 8 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 9 | 10 | @EnableZuulProxy 11 | @EnableEurekaClient 12 | @RibbonClients(defaultConfiguration = RibbonConfiguration.class) 13 | @SpringBootApplication 14 | public class GatewayApplication { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(GatewayApplication.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/configuration/HystrixFallbackConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway.configuration; 2 | 3 | import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.client.ClientHttpResponse; 10 | 11 | import java.io.ByteArrayInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | 15 | /** 16 | * @author moises.macero 17 | */ 18 | @Configuration 19 | public class HystrixFallbackConfiguration { 20 | 21 | @Bean 22 | public ZuulFallbackProvider zuulFallbackProvider() { 23 | return new ZuulFallbackProvider() { 24 | 25 | @Override 26 | public String getRoute() { 27 | // Might be confusing: it's the serviceId property and not the route 28 | return "multiplication"; 29 | } 30 | 31 | @Override 32 | public ClientHttpResponse fallbackResponse() { 33 | return new ClientHttpResponse() { 34 | @Override 35 | public HttpStatus getStatusCode() throws IOException { 36 | return HttpStatus.OK; 37 | } 38 | 39 | @Override 40 | public int getRawStatusCode() throws IOException { 41 | return HttpStatus.OK.value(); 42 | } 43 | 44 | @Override 45 | public String getStatusText() throws IOException { 46 | return HttpStatus.OK.toString(); 47 | } 48 | 49 | @Override 50 | public void close() {} 51 | 52 | @Override 53 | public InputStream getBody() throws IOException { 54 | return new ByteArrayInputStream("{\"factorA\":\"Sorry, Service is Down!\",\"factorB\":\"?\",\"id\":null}".getBytes()); 55 | } 56 | 57 | @Override 58 | public HttpHeaders getHeaders() { 59 | HttpHeaders headers = new HttpHeaders(); 60 | headers.setContentType(MediaType.APPLICATION_JSON); 61 | headers.setAccessControlAllowCredentials(true); 62 | headers.setAccessControlAllowOrigin("*"); 63 | return headers; 64 | } 65 | }; 66 | } 67 | }; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/configuration/RibbonConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway.configuration; 2 | 3 | import com.netflix.client.config.IClientConfig; 4 | import com.netflix.loadbalancer.*; 5 | import org.springframework.context.annotation.Bean; 6 | 7 | /** 8 | * @author moises.macero 9 | */ 10 | public class RibbonConfiguration { 11 | 12 | @Bean 13 | public IPing ribbonPing(final IClientConfig config) { 14 | return new PingUrl(false,"/health"); 15 | } 16 | 17 | @Bean 18 | public IRule ribbonRule(final IClientConfig config) { 19 | return new AvailabilityFilteringRule(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /gateway/src/main/java/microservices/book/gateway/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | public class WebConfiguration extends WebMvcConfigurerAdapter { 14 | 15 | /** 16 | * Enables Cross-Origin Resource Sharing (CORS) 17 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 18 | * @param registry 19 | */ 20 | @Override 21 | public void addCorsMappings(final CorsRegistry registry) { 22 | registry.addMapping("/**"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8000 3 | 4 | zuul: 5 | ignoredServices: '*' 6 | prefix: /api 7 | routes: 8 | multiplications: 9 | path: /multiplications/** 10 | serviceId: multiplication 11 | strip-prefix: false 12 | results: 13 | path: /results/** 14 | serviceId: multiplication 15 | strip-prefix: false 16 | leaders: 17 | path: /leaders/** 18 | serviceId: gamification 19 | strip-prefix: false 20 | stats: 21 | path: /stats/** 22 | serviceId: gamification 23 | strip-prefix: false 24 | 25 | endpoints: 26 | routes: 27 | sensitive: false 28 | trace: 29 | sensitive: false 30 | 31 | eureka: 32 | client: 33 | service-url: 34 | default-zone: http://localhost:8761/eureka/ 35 | -------------------------------------------------------------------------------- /gateway/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: gateway -------------------------------------------------------------------------------- /gateway/src/test/java/microservices/book/gateway/GatewayApplicationTests.java: -------------------------------------------------------------------------------- 1 | package microservices.book.gateway; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class GatewayApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /resources/logical_view_v8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/resources/logical_view_v8.png -------------------------------------------------------------------------------- /service-registry/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /service-registry/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/service-registry/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /service-registry/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /service-registry/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Migwn, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | # TODO classpath? 118 | fi 119 | 120 | if [ -z "$JAVA_HOME" ]; then 121 | javaExecutable="`which javac`" 122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 123 | # readlink(1) is not available as standard on Solaris 10. 124 | readLink=`which readlink` 125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 126 | if $darwin ; then 127 | javaHome="`dirname \"$javaExecutable\"`" 128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 129 | else 130 | javaExecutable="`readlink -f \"$javaExecutable\"`" 131 | fi 132 | javaHome="`dirname \"$javaExecutable\"`" 133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 134 | JAVA_HOME="$javaHome" 135 | export JAVA_HOME 136 | fi 137 | fi 138 | fi 139 | 140 | if [ -z "$JAVACMD" ] ; then 141 | if [ -n "$JAVA_HOME" ] ; then 142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 143 | # IBM's JDK on AIX uses strange locations for the executables 144 | JAVACMD="$JAVA_HOME/jre/sh/java" 145 | else 146 | JAVACMD="$JAVA_HOME/bin/java" 147 | fi 148 | else 149 | JAVACMD="`which java`" 150 | fi 151 | fi 152 | 153 | if [ ! -x "$JAVACMD" ] ; then 154 | echo "Error: JAVA_HOME is not defined correctly." >&2 155 | echo " We cannot execute $JAVACMD" >&2 156 | exit 1 157 | fi 158 | 159 | if [ -z "$JAVA_HOME" ] ; then 160 | echo "Warning: JAVA_HOME environment variable is not set." 161 | fi 162 | 163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 164 | 165 | # traverses directory structure from process work directory to filesystem root 166 | # first directory with .mvn subdirectory is considered project base directory 167 | find_maven_basedir() { 168 | 169 | if [ -z "$1" ] 170 | then 171 | echo "Path not specified to find_maven_basedir" 172 | return 1 173 | fi 174 | 175 | basedir="$1" 176 | wdir="$1" 177 | while [ "$wdir" != '/' ] ; do 178 | if [ -d "$wdir"/.mvn ] ; then 179 | basedir=$wdir 180 | break 181 | fi 182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 183 | if [ -d "${wdir}" ]; then 184 | wdir=`cd "$wdir/.."; pwd` 185 | fi 186 | # end of workaround 187 | done 188 | echo "${basedir}" 189 | } 190 | 191 | # concatenates all lines of a file 192 | concat_lines() { 193 | if [ -f "$1" ]; then 194 | echo "$(tr -s '\n' ' ' < "$1")" 195 | fi 196 | } 197 | 198 | BASE_DIR=`find_maven_basedir "$(pwd)"` 199 | if [ -z "$BASE_DIR" ]; then 200 | exit 1; 201 | fi 202 | 203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 204 | echo $MAVEN_PROJECTBASEDIR 205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 206 | 207 | # For Cygwin, switch paths to Windows format before running java 208 | if $cygwin; then 209 | [ -n "$M2_HOME" ] && 210 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 211 | [ -n "$JAVA_HOME" ] && 212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 213 | [ -n "$CLASSPATH" ] && 214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 215 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 217 | fi 218 | 219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 220 | 221 | exec "$JAVACMD" \ 222 | $MAVEN_OPTS \ 223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 226 | -------------------------------------------------------------------------------- /service-registry/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 84 | @REM Fallback to current working directory if not found. 85 | 86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 88 | 89 | set EXEC_DIR=%CD% 90 | set WDIR=%EXEC_DIR% 91 | :findBaseDir 92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 93 | cd .. 94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 95 | set WDIR=%CD% 96 | goto findBaseDir 97 | 98 | :baseDirFound 99 | set MAVEN_PROJECTBASEDIR=%WDIR% 100 | cd "%EXEC_DIR%" 101 | goto endDetectBaseDir 102 | 103 | :baseDirNotFound 104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 105 | cd "%EXEC_DIR%" 106 | 107 | :endDetectBaseDir 108 | 109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 110 | 111 | @setlocal EnableExtensions EnableDelayedExpansion 112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 114 | 115 | :endReadAdditionalConfig 116 | 117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 118 | 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 123 | if ERRORLEVEL 1 goto error 124 | goto end 125 | 126 | :error 127 | set ERROR_CODE=1 128 | 129 | :end 130 | @endlocal & set ERROR_CODE=%ERROR_CODE% 131 | 132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 136 | :skipRcPost 137 | 138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 140 | 141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 142 | 143 | exit /B %ERROR_CODE% 144 | -------------------------------------------------------------------------------- /service-registry/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | service-registry-v8 8 | 0.8.0-SNAPSHOT 9 | jar 10 | 11 | service-registry-v8 12 | Multiplication App - Service Registry (Microservices - the Practical Way book) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-eureka-server 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-actuator 37 | 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | test 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.cloud 50 | spring-cloud-dependencies 51 | ${spring-cloud.version} 52 | pom 53 | import 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-maven-plugin 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /service-registry/src/main/java/microservices/book/serviceregistry/ServiceRegistryApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.serviceregistry; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class ServiceRegistryApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ServiceRegistryApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /service-registry/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8761 -------------------------------------------------------------------------------- /service-registry/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=service-registry -------------------------------------------------------------------------------- /service-registry/src/test/java/microservices/book/serviceregistry/ServiceRegistryApplicationTests.java: -------------------------------------------------------------------------------- 1 | package microservices.book.serviceregistry; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ServiceRegistryApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /social-multiplication/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /social-multiplication/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/social-multiplication/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /social-multiplication/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip 2 | -------------------------------------------------------------------------------- /social-multiplication/README.md: -------------------------------------------------------------------------------- 1 | # microservices-v8 2 | 3 | This project contains the version 8 of the application that is developed under the scope of the book *Learn Microservices with Spring Boot*. You can get a copy of the book on [Apress](http://www.apress.com/gp/book/9781484231647). 4 | 5 | ## About this version 6 | 7 | This version contains four projects: 8 | * **gamification** and **social-multiplication** are our microservices already created and evolved in previous versions. 9 | * **ui** contains the web pages and javascript of our application, within an independently deployable component. 10 | * **gateway** contains the Spring Boot Application that models the API Gateway in our system, using Zuul. 11 | * **service-registry** contains the Eureka Registry Server, which is used now by the different microservices and the gateway. 12 | 13 | -------------------------------------------------------------------------------- /social-multiplication/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /social-multiplication/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 40 | 41 | @REM set %HOME% to equivalent of $HOME 42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 43 | 44 | @REM Execute a user defined script before this one 45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 49 | :skipRcPre 50 | 51 | @setlocal 52 | 53 | set ERROR_CODE=0 54 | 55 | @REM To isolate internal variables from possible post scripts, we use another setlocal 56 | @setlocal 57 | 58 | @REM ==== START VALIDATION ==== 59 | if not "%JAVA_HOME%" == "" goto OkJHome 60 | 61 | echo. 62 | echo Error: JAVA_HOME not found in your environment. >&2 63 | echo Please set the JAVA_HOME variable in your environment to match the >&2 64 | echo location of your Java installation. >&2 65 | echo. 66 | goto error 67 | 68 | :OkJHome 69 | if exist "%JAVA_HOME%\bin\java.exe" goto init 70 | 71 | echo. 72 | echo Error: JAVA_HOME is set to an invalid directory. >&2 73 | echo JAVA_HOME = "%JAVA_HOME%" >&2 74 | echo Please set the JAVA_HOME variable in your environment to match the >&2 75 | echo location of your Java installation. >&2 76 | echo. 77 | goto error 78 | 79 | @REM ==== END VALIDATION ==== 80 | 81 | :init 82 | 83 | set MAVEN_CMD_LINE_ARGS=%* 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | 121 | set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" 122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 123 | 124 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% 125 | if ERRORLEVEL 1 goto error 126 | goto end 127 | 128 | :error 129 | set ERROR_CODE=1 130 | 131 | :end 132 | @endlocal & set ERROR_CODE=%ERROR_CODE% 133 | 134 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 135 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 136 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 137 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 138 | :skipRcPost 139 | 140 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 141 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 142 | 143 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 144 | 145 | exit /B %ERROR_CODE% -------------------------------------------------------------------------------- /social-multiplication/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | microservices.book 7 | social-multiplication-v8 8 | 0.8.0-SNAPSHOT 9 | jar 10 | 11 | social-multiplication-v8 12 | Social Multiplication App (Learn Microservices with Spring Boot) 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.7.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Dalston.SR1 26 | 27 | 28 | 29 | 30 | 31 | org.springframework.cloud 32 | spring-cloud-dependencies 33 | ${spring-cloud.version} 34 | pom 35 | import 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-amqp 49 | 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-starter-eureka 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-actuator 59 | 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.16.18 66 | provided 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-starter-data-jpa 72 | 73 | 74 | 75 | com.h2database 76 | h2 77 | runtime 78 | 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-test 84 | test 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-maven-plugin 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/SocialMultiplicationApplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | 8 | @EnableEurekaClient 9 | @SpringBootApplication 10 | public class SocialMultiplicationApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SocialMultiplicationApplication.class, args); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/configuration/RabbitMQConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.configuration; 2 | 3 | import org.springframework.amqp.core.TopicExchange; 4 | import org.springframework.amqp.rabbit.connection.ConnectionFactory; 5 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 6 | import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * Configures RabbitMQ to use events in our application. 13 | */ 14 | @Configuration 15 | public class RabbitMQConfiguration { 16 | 17 | @Bean 18 | public TopicExchange multiplicationExchange(@Value("${multiplication.exchange}") final String exchangeName) { 19 | return new TopicExchange(exchangeName); 20 | } 21 | 22 | @Bean 23 | public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) { 24 | final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); 25 | rabbitTemplate.setMessageConverter(producerJackson2MessageConverter()); 26 | return rabbitTemplate; 27 | } 28 | 29 | @Bean 30 | public Jackson2JsonMessageConverter producerJackson2MessageConverter() { 31 | return new Jackson2JsonMessageConverter(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/configuration/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 5 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 7 | 8 | /** 9 | * @author moises.macero 10 | */ 11 | @Configuration 12 | @EnableWebMvc 13 | public class WebConfiguration extends WebMvcConfigurerAdapter { 14 | 15 | /** 16 | * Enables Cross-Origin Resource Sharing (CORS) 17 | * More info: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cors.html 18 | * @param registry 19 | */ 20 | @Override 21 | public void addCorsMappings(final CorsRegistry registry) { 22 | registry.addMapping("/**"); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/controller/MultiplicationController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.service.MultiplicationService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | /** 13 | * This class implements a REST API for our Multiplication application. 14 | */ 15 | @Slf4j 16 | @RestController 17 | @RequestMapping("/multiplications") 18 | final class MultiplicationController { 19 | 20 | private final MultiplicationService multiplicationService; 21 | 22 | private final int serverPort; 23 | 24 | @Autowired 25 | public MultiplicationController(final MultiplicationService multiplicationService, @Value("${server.port}") int serverPort) { 26 | this.multiplicationService = multiplicationService; 27 | this.serverPort = serverPort; 28 | } 29 | 30 | @GetMapping("/random") 31 | Multiplication getRandomMultiplication() { 32 | log.info("Generating a random multiplication from server @ {}", serverPort); 33 | return multiplicationService.createRandomMultiplication(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/controller/MultiplicationResultAttemptController.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | import microservices.book.multiplication.service.MultiplicationService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * This class provides a REST API to POST the attempts from users. 15 | */ 16 | @Slf4j 17 | @RestController 18 | @RequestMapping("/results") 19 | final class MultiplicationResultAttemptController { 20 | 21 | private final MultiplicationService multiplicationService; 22 | 23 | private final int serverPort; 24 | 25 | @Autowired 26 | MultiplicationResultAttemptController( 27 | final MultiplicationService multiplicationService, 28 | @Value("${server.port}") int serverPort) { 29 | this.multiplicationService = multiplicationService; 30 | this.serverPort = serverPort; 31 | } 32 | 33 | @PostMapping 34 | ResponseEntity postResult( 35 | final @RequestBody MultiplicationResultAttempt 36 | multiplicationResultAttempt) { 37 | return ResponseEntity.ok( 38 | multiplicationService.checkAttempt(multiplicationResultAttempt) 39 | ); 40 | } 41 | 42 | @GetMapping 43 | ResponseEntity> getStatistics( 44 | final @RequestParam("alias") String alias) { 45 | return ResponseEntity.ok( 46 | multiplicationService.getStatsForUser(alias) 47 | ); 48 | } 49 | 50 | @GetMapping("/{resultId}") 51 | ResponseEntity getResultById(final @PathVariable("resultId") Long resultId) { 52 | log.info("Retrieving result {} from server @ {}", resultId, serverPort); 53 | return ResponseEntity.ok( 54 | multiplicationService.getResultById(resultId) 55 | ); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/domain/Multiplication.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * This class represents a Multiplication (a * b). 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @ToString 19 | @EqualsAndHashCode 20 | @Entity 21 | public final class Multiplication { 22 | 23 | @Id 24 | @GeneratedValue 25 | @Column(name = "MULTIPLICATION_ID") 26 | private Long id; 27 | 28 | // Both factors 29 | private final int factorA; 30 | private final int factorB; 31 | 32 | // Empty constructor for JSON/JPA 33 | Multiplication() { 34 | this(0, 0); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/domain/MultiplicationResultAttempt.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.*; 9 | 10 | /** 11 | * Identifies the attempt from a {@link User} to solve a 12 | * {@link Multiplication}. 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | @ToString 17 | @EqualsAndHashCode 18 | @Entity 19 | public final class MultiplicationResultAttempt { 20 | 21 | @Id 22 | @GeneratedValue 23 | private Long id; 24 | 25 | @ManyToOne(cascade = CascadeType.PERSIST) 26 | @JoinColumn(name = "USER_ID") 27 | private final User user; 28 | 29 | @ManyToOne(cascade = CascadeType.PERSIST) 30 | @JoinColumn(name = "MULTIPLICATION_ID") 31 | private final Multiplication multiplication; 32 | private final int resultAttempt; 33 | 34 | private final boolean correct; 35 | 36 | // Empty constructor for JSON/JPA 37 | MultiplicationResultAttempt() { 38 | user = null; 39 | multiplication = null; 40 | resultAttempt = -1; 41 | correct = false; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/domain/User.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.domain; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | 13 | /** 14 | * Stores information to identify the user. 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @ToString 19 | @EqualsAndHashCode 20 | @Entity 21 | public final class User { 22 | 23 | @Id 24 | @GeneratedValue 25 | @Column(name = "USER_ID") 26 | private Long id; 27 | 28 | private final String alias; 29 | 30 | // Empty constructor for JSON/JPA 31 | protected User() { 32 | alias = null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/event/EventDispatcher.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.event; 2 | 3 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Component; 7 | 8 | /** 9 | * Handles the communication with the Event Bus. 10 | */ 11 | @Component 12 | public class EventDispatcher { 13 | 14 | private RabbitTemplate rabbitTemplate; 15 | 16 | // The exchange to use to send anything related to Multiplication 17 | private String multiplicationExchange; 18 | 19 | // The routing key to use to send this particular event 20 | private String multiplicationSolvedRoutingKey; 21 | 22 | @Autowired 23 | EventDispatcher(final RabbitTemplate rabbitTemplate, 24 | @Value("${multiplication.exchange}") final String multiplicationExchange, 25 | @Value("${multiplication.solved.key}") final String multiplicationSolvedRoutingKey) { 26 | this.rabbitTemplate = rabbitTemplate; 27 | this.multiplicationExchange = multiplicationExchange; 28 | this.multiplicationSolvedRoutingKey = multiplicationSolvedRoutingKey; 29 | } 30 | 31 | public void send(final MultiplicationSolvedEvent multiplicationSolvedEvent) { 32 | rabbitTemplate.convertAndSend( 33 | multiplicationExchange, 34 | multiplicationSolvedRoutingKey, 35 | multiplicationSolvedEvent); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/event/MultiplicationSolvedEvent.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.event; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * Event that models the fact that a {@link microservices.book.multiplication.domain.Multiplication} 12 | * has been solved in the system. Provides some context information about the multiplication. 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | @ToString 17 | @EqualsAndHashCode 18 | public class MultiplicationSolvedEvent implements Serializable { 19 | 20 | private final Long multiplicationResultAttemptId; 21 | private final Long userId; 22 | private final boolean correct; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/repository/MultiplicationRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.repository; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | /** 7 | * This interface allows us to save and retrieve Multiplications 8 | */ 9 | public interface MultiplicationRepository extends CrudRepository { 10 | } 11 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/repository/MultiplicationResultAttemptRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.repository; 2 | 3 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * This interface allow us to store and retrieve attempts 10 | */ 11 | public interface MultiplicationResultAttemptRepository 12 | extends CrudRepository { 13 | 14 | /** 15 | * @return the latest 5 attempts for a given user, identified by their alias. 16 | */ 17 | List findTop5ByUserAliasOrderByIdDesc(String userAlias); 18 | } 19 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.repository; 2 | 3 | import microservices.book.multiplication.domain.User; 4 | import org.springframework.data.repository.CrudRepository; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * This interface allows us to save and retrieve Users 10 | */ 11 | public interface UserRepository extends CrudRepository { 12 | 13 | Optional findByAlias(final String alias); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/MultiplicationService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | 6 | import java.util.List; 7 | 8 | public interface MultiplicationService { 9 | 10 | /** 11 | * Creates a Multiplication object with two randomly-generated factors 12 | * between 11 and 99. 13 | * 14 | * @return a Multiplication object with random factors 15 | */ 16 | Multiplication createRandomMultiplication(); 17 | 18 | /** 19 | * @return a {@link MultiplicationResultAttempt}, which contains information about the attempt and 20 | * indicates if it's correct or not. 21 | */ 22 | MultiplicationResultAttempt checkAttempt(final MultiplicationResultAttempt resultAttempt); 23 | 24 | /** 25 | * Gets the statistics for a given user. 26 | * 27 | * @param userAlias the user's alias 28 | * @return a list of {@link MultiplicationResultAttempt} objects, being the past attempts of the user. 29 | */ 30 | List getStatsForUser(final String userAlias); 31 | 32 | /** 33 | * Gets an attempt by its id 34 | * 35 | * @param resultId the identifier of the attempt 36 | * @return the {@link MultiplicationResultAttempt} object matching the id, otherwise null. 37 | */ 38 | MultiplicationResultAttempt getResultById(final Long resultId); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/MultiplicationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | import microservices.book.multiplication.domain.User; 6 | import microservices.book.multiplication.event.EventDispatcher; 7 | import microservices.book.multiplication.event.MultiplicationSolvedEvent; 8 | import microservices.book.multiplication.repository.MultiplicationResultAttemptRepository; 9 | import microservices.book.multiplication.repository.UserRepository; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.util.Assert; 13 | 14 | import javax.transaction.Transactional; 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | @Service 19 | class MultiplicationServiceImpl implements MultiplicationService { 20 | 21 | private RandomGeneratorService randomGeneratorService; 22 | private MultiplicationResultAttemptRepository attemptRepository; 23 | private UserRepository userRepository; 24 | private EventDispatcher eventDispatcher; 25 | 26 | @Autowired 27 | public MultiplicationServiceImpl(final RandomGeneratorService randomGeneratorService, 28 | final MultiplicationResultAttemptRepository attemptRepository, 29 | final UserRepository userRepository, 30 | final EventDispatcher eventDispatcher) { 31 | this.randomGeneratorService = randomGeneratorService; 32 | this.attemptRepository = attemptRepository; 33 | this.userRepository = userRepository; 34 | this.eventDispatcher = eventDispatcher; 35 | } 36 | 37 | @Override 38 | public Multiplication createRandomMultiplication() { 39 | int factorA = randomGeneratorService.generateRandomFactor(); 40 | int factorB = randomGeneratorService.generateRandomFactor(); 41 | return new Multiplication(factorA, factorB); 42 | } 43 | 44 | @Transactional 45 | @Override 46 | public MultiplicationResultAttempt checkAttempt(final MultiplicationResultAttempt attempt) { 47 | // Check if the user already exists for that alias 48 | Optional user = userRepository.findByAlias(attempt.getUser().getAlias()); 49 | 50 | // Avoids 'hack' attempts 51 | Assert.isTrue(!attempt.isCorrect(), "You can't send an attempt marked as correct!!"); 52 | 53 | // Check if the attempt is correct 54 | boolean isCorrect = attempt.getResultAttempt() == 55 | attempt.getMultiplication().getFactorA() * 56 | attempt.getMultiplication().getFactorB(); 57 | 58 | MultiplicationResultAttempt checkedAttempt = new MultiplicationResultAttempt( 59 | user.orElse(attempt.getUser()), 60 | attempt.getMultiplication(), 61 | attempt.getResultAttempt(), 62 | isCorrect 63 | ); 64 | 65 | // Stores the attempt 66 | MultiplicationResultAttempt storedAttempt = attemptRepository.save(checkedAttempt); 67 | 68 | // Communicates the result via Event 69 | eventDispatcher.send( 70 | new MultiplicationSolvedEvent(checkedAttempt.getId(), 71 | checkedAttempt.getUser().getId(), 72 | checkedAttempt.isCorrect()) 73 | ); 74 | 75 | return storedAttempt; 76 | } 77 | 78 | @Override 79 | public List getStatsForUser(final String userAlias) { 80 | return attemptRepository.findTop5ByUserAliasOrderByIdDesc(userAlias); 81 | } 82 | 83 | @Override 84 | public MultiplicationResultAttempt getResultById(final Long resultId) { 85 | return attemptRepository.findOne(resultId); 86 | } 87 | 88 | 89 | } 90 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/RandomGeneratorService.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | public interface RandomGeneratorService { 4 | 5 | /** 6 | * @return a randomly-generated factor. It's always a number between 11 and 99. 7 | */ 8 | int generateRandomFactor(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /social-multiplication/src/main/java/microservices/book/multiplication/service/RandomGeneratorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import org.springframework.stereotype.Service; 4 | import java.util.Random; 5 | 6 | @Service 7 | final class RandomGeneratorServiceImpl implements RandomGeneratorService { 8 | 9 | final static int MINIMUM_FACTOR = 11; 10 | final static int MAXIMUM_FACTOR = 99; 11 | 12 | @Override 13 | public int generateRandomFactor() { 14 | return new Random().nextInt((MAXIMUM_FACTOR - MINIMUM_FACTOR) + 1) + MINIMUM_FACTOR; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /social-multiplication/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | 3 | # Gives us access to the H2 database web console 4 | spring.h2.console.enabled=true 5 | # Generates the database *only* if it's not there yet 6 | spring.jpa.hibernate.ddl-auto=update 7 | # Creates the database in a file 8 | spring.datasource.url=jdbc:h2:file:~/social-multiplication;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=TRUE 9 | # For educational purposes we will show the SQL in console 10 | spring.jpa.properties.hibernate.show_sql=true 11 | 12 | ## RabbitMQ configuration 13 | multiplication.exchange=multiplication_exchange 14 | multiplication.solved.key=multiplication.solved -------------------------------------------------------------------------------- /social-multiplication/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=multiplication -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/MultiplicationControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.service.MultiplicationService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 11 | import org.springframework.boot.test.json.JacksonTester; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.mock.web.MockHttpServletResponse; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.mockito.BDDMockito.given; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 22 | 23 | @RunWith(SpringRunner.class) 24 | @WebMvcTest(MultiplicationController.class) 25 | public class MultiplicationControllerTest { 26 | 27 | @MockBean 28 | private MultiplicationService multiplicationService; 29 | 30 | @Autowired 31 | private MockMvc mvc; 32 | 33 | // This object will be magically initialized by the initFields method below. 34 | private JacksonTester json; 35 | 36 | @Before 37 | public void setup() { 38 | JacksonTester.initFields(this, new ObjectMapper()); 39 | } 40 | 41 | @Test 42 | public void getRandomMultiplicationTest() throws Exception{ 43 | // given 44 | given(multiplicationService.createRandomMultiplication()) 45 | .willReturn(new Multiplication(70, 20)); 46 | 47 | // when 48 | MockHttpServletResponse response = mvc.perform( 49 | get("/multiplications/random") 50 | .accept(MediaType.APPLICATION_JSON)) 51 | .andReturn().getResponse(); 52 | 53 | // then 54 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 55 | assertThat(response.getContentAsString()) 56 | .isEqualTo(json.write(new Multiplication(70, 20)).getJson()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/controller/MultiplicationResultAttemptControllerTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.controller; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import microservices.book.multiplication.domain.Multiplication; 5 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 6 | import microservices.book.multiplication.domain.User; 7 | import microservices.book.multiplication.service.MultiplicationService; 8 | import org.assertj.core.util.Lists; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 14 | import org.springframework.boot.test.json.JacksonTester; 15 | import org.springframework.boot.test.mock.mockito.MockBean; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.MediaType; 18 | import org.springframework.mock.web.MockHttpServletResponse; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | import org.springframework.test.web.servlet.MockMvc; 21 | 22 | import java.util.List; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | import static org.mockito.BDDMockito.given; 26 | import static org.mockito.Matchers.any; 27 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 28 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 29 | 30 | @RunWith(SpringRunner.class) 31 | @WebMvcTest(MultiplicationResultAttemptController.class) 32 | public class MultiplicationResultAttemptControllerTest { 33 | 34 | @MockBean 35 | private MultiplicationService multiplicationService; 36 | 37 | @Autowired 38 | private MockMvc mvc; 39 | 40 | // These objects will be magically initialized by the initFields method below. 41 | private JacksonTester jsonResultAttempt; 42 | private JacksonTester> jsonResultAttemptList; 43 | 44 | @Before 45 | public void setup() { 46 | JacksonTester.initFields(this, new ObjectMapper()); 47 | } 48 | 49 | @Test 50 | public void postResultReturnCorrect() throws Exception { 51 | genericParameterizedTest(true); 52 | } 53 | 54 | @Test 55 | public void postResultReturnNotCorrect() throws Exception { 56 | genericParameterizedTest(false); 57 | } 58 | 59 | void genericParameterizedTest(final boolean correct) throws Exception { 60 | // given (remember we're not testing here the service itself) 61 | User user = new User("john"); 62 | Multiplication multiplication = new Multiplication(50, 70); 63 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 64 | user, multiplication, 3500, correct); 65 | given(multiplicationService 66 | .checkAttempt(any(MultiplicationResultAttempt.class))) 67 | .willReturn(attempt); 68 | 69 | // when 70 | MockHttpServletResponse response = mvc.perform( 71 | post("/results").contentType(MediaType.APPLICATION_JSON) 72 | .content(jsonResultAttempt.write(attempt).getJson())) 73 | .andReturn().getResponse(); 74 | 75 | // then 76 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 77 | assertThat(response.getContentAsString()).isEqualTo( 78 | jsonResultAttempt.write( 79 | new MultiplicationResultAttempt(attempt.getUser(), 80 | attempt.getMultiplication(), 81 | attempt.getResultAttempt(), 82 | correct) 83 | ).getJson()); 84 | } 85 | 86 | @Test 87 | public void getUserStats() throws Exception { 88 | // given 89 | User user = new User("john_doe"); 90 | Multiplication multiplication = new Multiplication(50, 70); 91 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 92 | user, multiplication, 3500, true); 93 | List recentAttempts = Lists.newArrayList(attempt, attempt); 94 | given(multiplicationService 95 | .getStatsForUser("john_doe")) 96 | .willReturn(recentAttempts); 97 | 98 | // when 99 | MockHttpServletResponse response = mvc.perform( 100 | get("/results").param("alias", "john_doe")) 101 | .andReturn().getResponse(); 102 | 103 | // then 104 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 105 | assertThat(response.getContentAsString()).isEqualTo( 106 | jsonResultAttemptList.write( 107 | recentAttempts 108 | ).getJson()); 109 | } 110 | 111 | @Test 112 | public void getResultByIdTest() throws Exception { 113 | // given 114 | User user = new User("john_doe"); 115 | Multiplication multiplication = new Multiplication(50, 70); 116 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 117 | user, multiplication, 3500, true); 118 | given(multiplicationService.getResultById(4L)).willReturn(attempt); 119 | 120 | // when 121 | MockHttpServletResponse response = mvc.perform( 122 | get("/results/4")) 123 | .andReturn().getResponse(); 124 | 125 | // then 126 | assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); 127 | assertThat(response.getContentAsString()).isEqualTo( 128 | jsonResultAttempt.write(attempt).getJson()); 129 | } 130 | 131 | } -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/service/MultiplicationServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import microservices.book.multiplication.domain.Multiplication; 4 | import microservices.book.multiplication.domain.MultiplicationResultAttempt; 5 | import microservices.book.multiplication.domain.User; 6 | import microservices.book.multiplication.event.EventDispatcher; 7 | import microservices.book.multiplication.event.MultiplicationSolvedEvent; 8 | import microservices.book.multiplication.repository.MultiplicationResultAttemptRepository; 9 | import microservices.book.multiplication.repository.UserRepository; 10 | import org.assertj.core.util.Lists; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.mockito.Mock; 14 | import org.mockito.MockitoAnnotations; 15 | 16 | import java.util.List; 17 | import java.util.Optional; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | import static org.mockito.BDDMockito.given; 21 | import static org.mockito.Matchers.eq; 22 | import static org.mockito.Mockito.verify; 23 | 24 | public class MultiplicationServiceImplTest { 25 | 26 | private MultiplicationServiceImpl multiplicationServiceImpl; 27 | 28 | @Mock 29 | private RandomGeneratorService randomGeneratorService; 30 | 31 | @Mock 32 | private MultiplicationResultAttemptRepository attemptRepository; 33 | 34 | @Mock 35 | private UserRepository userRepository; 36 | 37 | @Mock 38 | private EventDispatcher eventDispatcher; 39 | 40 | @Before 41 | public void setUp() { 42 | // With this call to initMocks we tell Mockito to process the annotations 43 | MockitoAnnotations.initMocks(this); 44 | multiplicationServiceImpl = new MultiplicationServiceImpl(randomGeneratorService, attemptRepository, 45 | userRepository, eventDispatcher); 46 | } 47 | 48 | @Test 49 | public void createRandomMultiplicationTest() { 50 | // given (our mocked Random Generator service will return first 50, then 30) 51 | given(randomGeneratorService.generateRandomFactor()).willReturn(50, 30); 52 | 53 | // when 54 | Multiplication multiplication = multiplicationServiceImpl.createRandomMultiplication(); 55 | 56 | // then 57 | assertThat(multiplication.getFactorA()).isEqualTo(50); 58 | assertThat(multiplication.getFactorB()).isEqualTo(30); 59 | } 60 | 61 | @Test 62 | public void checkCorrectAttemptTest() { 63 | // given 64 | Multiplication multiplication = new Multiplication(50, 60); 65 | User user = new User("john_doe"); 66 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 67 | user, multiplication, 3000, false); 68 | MultiplicationResultAttempt verifiedAttempt = new MultiplicationResultAttempt( 69 | user, multiplication, 3000, true); 70 | MultiplicationSolvedEvent event = new MultiplicationSolvedEvent(attempt.getId(), 71 | attempt.getUser().getId(), true); 72 | given(userRepository.findByAlias("john_doe")).willReturn(Optional.empty()); 73 | // Note: the service will set correct to true 74 | given(attemptRepository.save(verifiedAttempt)).willReturn(verifiedAttempt); 75 | 76 | // when 77 | MultiplicationResultAttempt resultAttempt = multiplicationServiceImpl.checkAttempt(attempt); 78 | 79 | // then 80 | assertThat(resultAttempt.isCorrect()).isTrue(); 81 | verify(attemptRepository).save(verifiedAttempt); 82 | verify(eventDispatcher).send(eq(event)); 83 | } 84 | 85 | @Test 86 | public void checkWrongAttemptTest() { 87 | // given 88 | Multiplication multiplication = new Multiplication(50, 60); 89 | User user = new User("john_doe"); 90 | MultiplicationResultAttempt attempt = new MultiplicationResultAttempt( 91 | user, multiplication, 3010, false); 92 | MultiplicationResultAttempt storedAttempt = new MultiplicationResultAttempt( 93 | user, multiplication, 3010, false); 94 | MultiplicationSolvedEvent event = new MultiplicationSolvedEvent(attempt.getId(), 95 | attempt.getUser().getId(), false); 96 | given(userRepository.findByAlias("john_doe")).willReturn(Optional.empty()); 97 | given(attemptRepository.save(attempt)).willReturn(storedAttempt); 98 | 99 | // when 100 | MultiplicationResultAttempt resultAttempt = multiplicationServiceImpl.checkAttempt(attempt); 101 | 102 | // then 103 | assertThat(resultAttempt.isCorrect()).isFalse(); 104 | verify(attemptRepository).save(attempt); 105 | verify(eventDispatcher).send(eq(event)); 106 | } 107 | 108 | @Test 109 | public void retrieveStatsTest() { 110 | // given 111 | Multiplication multiplication = new Multiplication(50, 60); 112 | User user = new User("john_doe"); 113 | MultiplicationResultAttempt attempt1 = new MultiplicationResultAttempt( 114 | user, multiplication, 3010, false); 115 | MultiplicationResultAttempt attempt2 = new MultiplicationResultAttempt( 116 | user, multiplication, 3051, false); 117 | List latestAttempts = Lists.newArrayList(attempt1, attempt2); 118 | given(userRepository.findByAlias("john_doe")).willReturn(Optional.empty()); 119 | given(attemptRepository.findTop5ByUserAliasOrderByIdDesc("john_doe")) 120 | .willReturn(latestAttempts); 121 | 122 | // when 123 | List latestAttemptsResult = 124 | multiplicationServiceImpl.getStatsForUser("john_doe"); 125 | 126 | // then 127 | assertThat(latestAttemptsResult).isEqualTo(latestAttempts); 128 | } 129 | } -------------------------------------------------------------------------------- /social-multiplication/src/test/java/microservices/book/multiplication/service/RandomGeneratorServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package microservices.book.multiplication.service; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | import java.util.stream.IntStream; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | public class RandomGeneratorServiceImplTest { 13 | 14 | private RandomGeneratorServiceImpl randomGeneratorServiceImpl; 15 | 16 | @Before 17 | public void setUp() { 18 | randomGeneratorServiceImpl = new RandomGeneratorServiceImpl(); 19 | } 20 | 21 | @Test 22 | public void generateRandomFactorIsBetweenExpectedLimits() throws Exception { 23 | // when a good sample of randomly generated factors is generated 24 | List randomFactors = IntStream.range(0, 1000) 25 | .map(i -> randomGeneratorServiceImpl.generateRandomFactor()) 26 | .boxed().collect(Collectors.toList()); 27 | 28 | // then all of them should be between 11 and 100 29 | // because we want a middle-complexity calculation 30 | assertThat(randomFactors).containsOnlyElementsOf(IntStream.range(11, 100) 31 | .boxed().collect(Collectors.toList())); 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /ui/start.d/deploy.ini: -------------------------------------------------------------------------------- 1 | # --------------------------------------- 2 | # Module: deploy 3 | # Enables webapplication deployment from the webapps directory. 4 | # --------------------------------------- 5 | --module=deploy 6 | 7 | # Monitored directory name (relative to $jetty.base) 8 | # jetty.deploy.monitoredDir=webapps 9 | # - OR - 10 | # Monitored directory path (fully qualified) 11 | # jetty.deploy.monitoredPath=/var/www/webapps 12 | 13 | # Defaults Descriptor for all deployed webapps 14 | # jetty.deploy.defaultsDescriptorPath=${jetty.base}/etc/webdefault.xml 15 | 16 | # Monitored directory scan period (seconds) 17 | # jetty.deploy.scanInterval=1 18 | 19 | # Whether to extract *.war files 20 | # jetty.deploy.extractWars=true 21 | 22 | -------------------------------------------------------------------------------- /ui/start.d/http.ini: -------------------------------------------------------------------------------- 1 | # --------------------------------------- 2 | # Module: http 3 | # Enables a HTTP connector on the server. 4 | # By default HTTP/1 is support, but HTTP2C can 5 | # be added to the connector with the http2c module. 6 | # --------------------------------------- 7 | --module=http 8 | 9 | ### HTTP Connector Configuration 10 | 11 | ## Connector host/address to bind to 12 | # jetty.http.host=0.0.0.0 13 | 14 | ## Connector port to listen on 15 | jetty.http.port=9090 16 | 17 | ## Connector idle timeout in milliseconds 18 | # jetty.http.idleTimeout=30000 19 | 20 | ## Connector socket linger time in seconds (-1 to disable) 21 | # jetty.http.soLingerTime=-1 22 | 23 | ## Number of acceptors (-1 picks default based on number of cores) 24 | # jetty.http.acceptors=-1 25 | 26 | ## Number of selectors (-1 picks default based on number of cores) 27 | # jetty.http.selectors=-1 28 | 29 | ## ServerSocketChannel backlog (0 picks platform default) 30 | # jetty.http.acceptorQueueSize=0 31 | 32 | ## Thread priority delta to give to acceptor threads 33 | # jetty.http.acceptorPriorityDelta=0 34 | 35 | ## HTTP Compliance: RFC7230, RFC2616, LEGACY 36 | # jetty.http.compliance=RFC7230 37 | 38 | -------------------------------------------------------------------------------- /ui/webapps/ui/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/ui/webapps/ui/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/ui/webapps/ui/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/ui/webapps/ui/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /ui/webapps/ui/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microservices-practical/microservices-v8/a76286169d797c27727af163512afb679d3a3432/ui/webapps/ui/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /ui/webapps/ui/gamification-client.js: -------------------------------------------------------------------------------- 1 | var SERVER_URL = "http://localhost:8000/api"; 2 | 3 | function updateLeaderBoard() { 4 | $.ajax({ 5 | url: SERVER_URL + "/leaders" 6 | }).then(function(data) { 7 | $('#leaderboard-body').empty(); 8 | data.forEach(function(row) { 9 | $('#leaderboard-body').append('' + row.userId + '' + 10 | '' + row.totalScore + ''); 11 | }); 12 | }); 13 | } 14 | 15 | function updateStats(userId) { 16 | $.ajax({ 17 | url: SERVER_URL + "/stats?userId=" + userId, 18 | success: function(data) { 19 | $('#stats-div').show(); 20 | $('#stats-user-id').empty().append(userId); 21 | $('#stats-score').empty().append(data.score); 22 | $('#stats-badges').empty().append(data.badges.join()); 23 | }, 24 | error: function(data) { 25 | $('#stats-div').show(); 26 | $('#stats-user-id').empty().append(userId); 27 | $('#stats-score').empty().append(0); 28 | $('#stats-badges').empty(); 29 | } 30 | }); 31 | } 32 | 33 | $(document).ready(function() { 34 | 35 | updateLeaderBoard(); 36 | 37 | $("#refresh-leaderboard").click(function( event ) { 38 | updateLeaderBoard(); 39 | }); 40 | 41 | }); 42 | -------------------------------------------------------------------------------- /ui/webapps/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Multiplication v1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |

Welcome to Social Multiplication

17 |
18 |
19 |
20 |
21 |

Your new challenge is

22 |

23 | x 24 |

25 |

26 |

27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 |

38 |
39 | 40 | 59 | 60 |
61 |
62 |

Leaderboard

63 | 64 | 65 | 66 | 67 | 68 | 69 |
User IDScore
70 |
71 | 72 |
73 | 74 | 86 |
87 |
88 |
89 | 90 | 91 | -------------------------------------------------------------------------------- /ui/webapps/ui/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /ui/webapps/ui/multiplication-client.js: -------------------------------------------------------------------------------- 1 | var SERVER_URL = "http://localhost:8000/api"; 2 | 3 | function updateMultiplication() { 4 | $.ajax({ 5 | url: SERVER_URL + "/multiplications/random" 6 | }).then(function(data) { 7 | // Cleans the form 8 | $("#attempt-form").find( "input[name='result-attempt']" ).val(""); 9 | $("#attempt-form").find( "input[name='user-alias']" ).val(""); 10 | // Gets a random challenge from API and loads the data in the HTML 11 | $('.multiplication-a').empty().append(data.factorA); 12 | $('.multiplication-b').empty().append(data.factorB); 13 | }); 14 | } 15 | 16 | function updateResults(alias) { 17 | var userId = -1; 18 | $.ajax({ 19 | url: SERVER_URL + "/results?alias=" + alias, 20 | async: false, 21 | success: function(data) { 22 | $('#results-div').show(); 23 | $('#results-body').empty(); 24 | data.forEach(function(row) { 25 | $('#results-body').append('' + row.id + '' + 26 | '' + row.multiplication.factorA + ' x ' + row.multiplication.factorB + '' + 27 | '' + row.resultAttempt + '' + 28 | '' + (row.correct === true ? 'YES' : 'NO') + ''); 29 | }); 30 | userId = data[0].user.id; 31 | } 32 | }); 33 | return userId; 34 | } 35 | 36 | $(document).ready(function() { 37 | 38 | updateMultiplication(); 39 | 40 | $("#attempt-form").submit(function( event ) { 41 | 42 | // Don't submit the form normally 43 | event.preventDefault(); 44 | 45 | // Get some values from elements on the page 46 | var a = $('.multiplication-a').text(); 47 | var b = $('.multiplication-b').text(); 48 | var $form = $( this ), 49 | attempt = $form.find( "input[name='result-attempt']" ).val(), 50 | userAlias = $form.find( "input[name='user-alias']" ).val(); 51 | 52 | // Compose the data in the format that the API is expecting 53 | var data = { user: { alias: userAlias}, multiplication: {factorA: a, factorB: b}, resultAttempt: attempt}; 54 | 55 | // Send the data using post 56 | $.ajax({ 57 | url: SERVER_URL + '/results', 58 | type: 'POST', 59 | data: JSON.stringify(data), 60 | contentType: "application/json; charset=utf-8", 61 | dataType: "json", 62 | async: false, 63 | success: function(result){ 64 | if(result.correct) { 65 | $('.result-message').empty() 66 | .append("

The result is correct! Congratulations!

"); 67 | } else { 68 | $('.result-message').empty() 69 | .append("

Ooops that's not correct! But keep trying!

"); 70 | } 71 | } 72 | }); 73 | 74 | updateMultiplication(); 75 | 76 | setTimeout(function(){ 77 | var userId = updateResults(userAlias); 78 | updateStats(userId); 79 | updateLeaderBoard(); 80 | }, 300); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /ui/webapps/ui/styles.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | html { 6 | display: table; 7 | margin: auto; 8 | } 9 | 10 | body { 11 | display: table-cell; 12 | vertical-align: middle; 13 | } --------------------------------------------------------------------------------