├── README.md └── user-account-registration ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── codebyamir │ │ ├── UserAccountRegistrationApplication.java │ │ ├── configuration │ │ ├── MvcConfig.java │ │ └── SecurityConfiguration.java │ │ ├── controller │ │ └── RegisterController.java │ │ ├── model │ │ └── User.java │ │ ├── repository │ │ └── UserRepository.java │ │ └── service │ │ ├── EmailService.java │ │ └── UserService.java └── resources │ ├── application.properties │ ├── static │ ├── css │ │ ├── fv.css │ │ └── style.css │ └── js │ │ └── fv.min.js │ └── templates │ ├── confirm.html │ └── register.html └── test └── java └── com └── codebyamir └── UserAccountRegistrationApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-user-account-registration 2 | User Account Registration with Spring Boot 3 | -------------------------------------------------------------------------------- /user-account-registration/.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/ -------------------------------------------------------------------------------- /user-account-registration/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codebyamir-zz/spring-boot-user-account-registration/24936c6f8c1604956295fc713c04489a0370bc7f/user-account-registration/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /user-account-registration/.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 | -------------------------------------------------------------------------------- /user-account-registration/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 | -------------------------------------------------------------------------------- /user-account-registration/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 | -------------------------------------------------------------------------------- /user-account-registration/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.codebyamir 7 | user-account-registration 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | user-account-registration 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.5.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-thymeleaf 35 | 36 | 37 | 38 | org.thymeleaf.extras 39 | thymeleaf-extras-springsecurity4 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-web 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-security 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-devtools 55 | runtime 56 | 57 | 58 | mysql 59 | mysql-connector-java 60 | runtime 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-test 65 | test 66 | 67 | 68 | 69 | com.nulab-inc 70 | zxcvbn 71 | 1.2.3 72 | 73 | 74 | 75 | net.sourceforge.nekohtml 76 | nekohtml 77 | 1.9.21 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-mail 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | org.springframework.boot 91 | spring-boot-maven-plugin 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/UserAccountRegistrationApplication.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | 7 | @SpringBootApplication 8 | @EnableAsync 9 | public class UserAccountRegistrationApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(UserAccountRegistrationApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/configuration/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.configuration; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 7 | 8 | @Configuration 9 | public class MvcConfig extends WebMvcConfigurerAdapter { 10 | 11 | @Bean 12 | public BCryptPasswordEncoder passwordEncoder() { 13 | BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); 14 | return bCryptPasswordEncoder; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/configuration/SecurityConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.configuration; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 7 | 8 | @Configuration 9 | @EnableWebSecurity 10 | public class SecurityConfiguration extends WebSecurityConfigurerAdapter { 11 | 12 | @Override 13 | protected void configure(HttpSecurity http) throws Exception { 14 | http.authorizeRequests() 15 | .antMatchers("/register").permitAll() 16 | .antMatchers("/confirm").permitAll(); 17 | 18 | } 19 | 20 | 21 | } -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/controller/RegisterController.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.controller; 2 | 3 | import java.util.Map; 4 | import java.util.UUID; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.validation.Valid; 8 | 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.mail.SimpleMailMessage; 11 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.validation.BindingResult; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.RequestParam; 17 | import org.springframework.web.servlet.ModelAndView; 18 | import org.springframework.web.servlet.mvc.support.RedirectAttributes; 19 | 20 | import com.codebyamir.model.User; 21 | import com.codebyamir.service.EmailService; 22 | import com.codebyamir.service.UserService; 23 | import com.nulabinc.zxcvbn.Strength; 24 | import com.nulabinc.zxcvbn.Zxcvbn; 25 | 26 | @Controller 27 | public class RegisterController { 28 | 29 | private BCryptPasswordEncoder bCryptPasswordEncoder; 30 | private UserService userService; 31 | private EmailService emailService; 32 | 33 | @Autowired 34 | public RegisterController(BCryptPasswordEncoder bCryptPasswordEncoder, 35 | UserService userService, EmailService emailService) { 36 | this.bCryptPasswordEncoder = bCryptPasswordEncoder; 37 | this.userService = userService; 38 | this.emailService = emailService; 39 | } 40 | 41 | // Return registration form template 42 | @RequestMapping(value="/register", method = RequestMethod.GET) 43 | public ModelAndView showRegistrationPage(ModelAndView modelAndView, User user){ 44 | modelAndView.addObject("user", user); 45 | modelAndView.setViewName("register"); 46 | return modelAndView; 47 | } 48 | 49 | // Process form input data 50 | @RequestMapping(value = "/register", method = RequestMethod.POST) 51 | public ModelAndView processRegistrationForm(ModelAndView modelAndView, @Valid User user, BindingResult bindingResult, HttpServletRequest request) { 52 | 53 | // Lookup user in database by e-mail 54 | User userExists = userService.findByEmail(user.getEmail()); 55 | 56 | System.out.println(userExists); 57 | 58 | if (userExists != null) { 59 | modelAndView.addObject("alreadyRegisteredMessage", "Oops! There is already a user registered with the email provided."); 60 | modelAndView.setViewName("register"); 61 | bindingResult.reject("email"); 62 | } 63 | 64 | if (bindingResult.hasErrors()) { 65 | modelAndView.setViewName("register"); 66 | } else { // new user so we create user and send confirmation e-mail 67 | 68 | // Disable user until they click on confirmation link in email 69 | user.setEnabled(false); 70 | 71 | // Generate random 36-character string token for confirmation link 72 | user.setConfirmationToken(UUID.randomUUID().toString()); 73 | 74 | userService.saveUser(user); 75 | 76 | String appUrl = request.getScheme() + "://" + request.getServerName(); 77 | 78 | SimpleMailMessage registrationEmail = new SimpleMailMessage(); 79 | registrationEmail.setTo(user.getEmail()); 80 | registrationEmail.setSubject("Registration Confirmation"); 81 | registrationEmail.setText("To confirm your e-mail address, please click the link below:\n" 82 | + appUrl + "/confirm?token=" + user.getConfirmationToken()); 83 | registrationEmail.setFrom("noreply@domain.com"); 84 | 85 | emailService.sendEmail(registrationEmail); 86 | 87 | modelAndView.addObject("confirmationMessage", "A confirmation e-mail has been sent to " + user.getEmail()); 88 | modelAndView.setViewName("register"); 89 | } 90 | 91 | return modelAndView; 92 | } 93 | 94 | // Process confirmation link 95 | @RequestMapping(value="/confirm", method = RequestMethod.GET) 96 | public ModelAndView confirmRegistration(ModelAndView modelAndView, @RequestParam("token") String token) { 97 | 98 | User user = userService.findByConfirmationToken(token); 99 | 100 | if (user == null) { // No token found in DB 101 | modelAndView.addObject("invalidToken", "Oops! This is an invalid confirmation link."); 102 | } else { // Token found 103 | modelAndView.addObject("confirmationToken", user.getConfirmationToken()); 104 | } 105 | 106 | modelAndView.setViewName("confirm"); 107 | return modelAndView; 108 | } 109 | 110 | // Process confirmation link 111 | @RequestMapping(value="/confirm", method = RequestMethod.POST) 112 | public ModelAndView confirmRegistration(ModelAndView modelAndView, BindingResult bindingResult, @RequestParam Map requestParams, RedirectAttributes redir) { 113 | 114 | modelAndView.setViewName("confirm"); 115 | 116 | Zxcvbn passwordCheck = new Zxcvbn(); 117 | 118 | Strength strength = passwordCheck.measure(requestParams.get("password")); 119 | 120 | if (strength.getScore() < 3) { 121 | //modelAndView.addObject("errorMessage", "Your password is too weak. Choose a stronger one."); 122 | bindingResult.reject("password"); 123 | 124 | redir.addFlashAttribute("errorMessage", "Your password is too weak. Choose a stronger one."); 125 | 126 | modelAndView.setViewName("redirect:confirm?token=" + requestParams.get("token")); 127 | System.out.println(requestParams.get("token")); 128 | return modelAndView; 129 | } 130 | 131 | // Find the user associated with the reset token 132 | User user = userService.findByConfirmationToken(requestParams.get("token")); 133 | 134 | // Set new password 135 | user.setPassword(bCryptPasswordEncoder.encode(requestParams.get("password"))); 136 | 137 | // Set user to enabled 138 | user.setEnabled(true); 139 | 140 | // Save user 141 | userService.saveUser(user); 142 | 143 | modelAndView.addObject("successMessage", "Your password has been set!"); 144 | return modelAndView; 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/model/User.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.model; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.GeneratedValue; 6 | import javax.persistence.GenerationType; 7 | import javax.persistence.Id; 8 | import javax.persistence.Table; 9 | 10 | import org.hibernate.validator.constraints.Email; 11 | import org.hibernate.validator.constraints.NotEmpty; 12 | import org.springframework.data.annotation.Transient; 13 | 14 | @Entity 15 | @Table(name = "user") 16 | public class User { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.AUTO) 20 | @Column(name = "id") 21 | private int id; 22 | 23 | @Column(name = "email", nullable = false, unique = true) 24 | @Email(message = "Please provide a valid e-mail") 25 | @NotEmpty(message = "Please provide an e-mail") 26 | private String email; 27 | 28 | @Column(name = "password") 29 | @Transient 30 | private String password; 31 | 32 | @Column(name = "first_name") 33 | @NotEmpty(message = "Please provide your first name") 34 | private String firstName; 35 | 36 | @Column(name = "last_name") 37 | @NotEmpty(message = "Please provide your last name") 38 | private String lastName; 39 | 40 | @Column(name = "enabled") 41 | private boolean enabled; 42 | 43 | @Column(name = "confirmation_token") 44 | private String confirmationToken; 45 | 46 | 47 | public String getConfirmationToken() { 48 | return confirmationToken; 49 | } 50 | 51 | public void setConfirmationToken(String confirmationToken) { 52 | this.confirmationToken = confirmationToken; 53 | } 54 | 55 | 56 | public int getId() { 57 | return id; 58 | } 59 | 60 | public void setId(int id) { 61 | this.id = id; 62 | } 63 | 64 | public String getPassword() { 65 | return password; 66 | } 67 | 68 | public void setPassword(String password) { 69 | this.password = password; 70 | } 71 | 72 | public String getFirstName() { 73 | return firstName; 74 | } 75 | 76 | public void setFirstName(String firstName) { 77 | this.firstName = firstName; 78 | } 79 | 80 | public String getLastName() { 81 | return lastName; 82 | } 83 | 84 | public void setLastName(String lastName) { 85 | this.lastName = lastName; 86 | } 87 | 88 | public String getEmail() { 89 | return email; 90 | } 91 | 92 | public void setEmail(String email) { 93 | this.email = email; 94 | } 95 | 96 | public boolean getEnabled() { 97 | return enabled; 98 | } 99 | 100 | public void setEnabled(boolean value) { 101 | this.enabled = value; 102 | } 103 | 104 | 105 | } -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import com.codebyamir.model.User; 7 | 8 | @Repository("userRepository") 9 | public interface UserRepository extends CrudRepository { 10 | User findByEmail(String email); 11 | User findByConfirmationToken(String confirmationToken); 12 | } -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/service/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.mail.SimpleMailMessage; 5 | import org.springframework.mail.javamail.JavaMailSender; 6 | import org.springframework.scheduling.annotation.Async; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service("emailService") 10 | public class EmailService { 11 | 12 | private JavaMailSender mailSender; 13 | 14 | @Autowired 15 | public EmailService(JavaMailSender mailSender) { 16 | this.mailSender = mailSender; 17 | } 18 | 19 | @Async 20 | public void sendEmail(SimpleMailMessage email) { 21 | mailSender.send(email); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /user-account-registration/src/main/java/com/codebyamir/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir.service; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.stereotype.Service; 5 | 6 | import com.codebyamir.model.User; 7 | import com.codebyamir.repository.UserRepository; 8 | 9 | @Service("userService") 10 | public class UserService { 11 | 12 | private UserRepository userRepository; 13 | 14 | @Autowired 15 | public UserService(UserRepository userRepository) { 16 | this.userRepository = userRepository; 17 | } 18 | 19 | public User findByEmail(String email) { 20 | return userRepository.findByEmail(email); 21 | } 22 | 23 | public User findByConfirmationToken(String confirmationToken) { 24 | return userRepository.findByConfirmationToken(confirmationToken); 25 | } 26 | 27 | public void saveUser(User user) { 28 | userRepository.save(user); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /user-account-registration/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # =============================== 2 | # TOMCAT 3 | # =============================== 4 | server.address=127.0.0.1 5 | server.error.whitelabel.enabled=false 6 | server.tomcat.accesslog.enabled=true 7 | 8 | # =============================== 9 | # SMTP EMAIL 10 | # =============================== 11 | 12 | spring.mail.host = 13 | spring.mail.username = 14 | spring.mail.password = 15 | spring.mail.port = 16 | spring.mail.properties.mail.smtp.auth = true 17 | spring.mail.properties.mail.smtp.starttls.enable = true 18 | 19 | 20 | # =============================== 21 | # = LOGGING 22 | # =============================== 23 | logging.level.org.springframework.web=DEBUG 24 | logging.level.org.hibernate=ERROR 25 | 26 | # =============================== 27 | # = DATA SOURCE 28 | # =============================== 29 | spring.datasource.url=jdbc:mysql://localhost:3306/demo_db?zeroDateTimeBehavior=convertToNull&useSSL=false 30 | spring.datasource.username=dbUser 31 | spring.datasource.password=dbPass 32 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 33 | spring.datasource.tomcat.max-wait=10000 34 | spring.datasource.tomcat.max-active=5 35 | spring.datasource.tomcat.test-on-borrow=true 36 | 37 | # =============================== 38 | # = JPA / HIBERNATE 39 | # =============================== 40 | spring.jpa.show-sql = true 41 | spring.jpa.hibernate.ddl-auto = create 42 | 43 | # =============================== 44 | # = Thymeleaf configurations 45 | # =============================== 46 | spring.thymeleaf.mode=LEGACYHTML5 47 | spring.thymeleaf.cache=false 48 | -------------------------------------------------------------------------------- /user-account-registration/src/main/resources/static/css/fv.css: -------------------------------------------------------------------------------- 1 | .form-inline.fv-form-bootstrap .form-group,.fv-form-bootstrap4.form-inline .form-group{vertical-align:top}.fv-has-feedback{position:relative}.fv-control-feedback{position:absolute;top:0;right:0;z-index:3;display:block;text-align:center}.fv-help-block{display:block}.fv-form [placeholder]::-ms-clear{display:none}.fv-form-foundation .form-error,.fv-form-uikit .uk-text-danger{display:block}.fv-form-bootstrap .help-block{margin-bottom:0}.fv-form-bootstrap .tooltip-inner{text-align:left}.fv-form-bootstrap .fv-bootstrap-icon-input-group{z-index:100}.fv-form-bootstrap4 .fv-control-feedback{width:38px;height:38px;line-height:38px}.fv-form-bootstrap4 .form-group.row .fv-control-feedback{right:15px}.fv-form-bootstrap4 .form-group.row .form-check~.fv-control-feedback{top:-7px}.fv-form-bootstrap4:not(.form-inline) label~.fv-control-feedback{top:32px}.fv-form-bootstrap4:not(.form-inline) label.sr-only~.fv-control-feedback{top:-7px}.fv-form-bootstrap4:not(.form-inline) .form-check~.fv-control-feedback{top:25px}.fv-form-bootstrap4 .has-success .fv-control-feedback,.fv-form-bootstrap4 .has-success label{color:#5cb85c}.fv-form-bootstrap4 .has-warning .fv-control-feedback,.fv-form-bootstrap4 .has-warning label{color:#f0ad4e}.fv-form-foundation5 .fv-control-feedback{right:15px;width:37px;height:37px;line-height:37px}.fv-form-foundation5 .row .row:not(.collapse) .fv-control-feedback{right:8px}.fv-form-foundation5 .row .collapse .fv-control-feedback{right:0}.fv-form-foundation5.fv-form-horizontal [type=checkbox]~.fv-control-feedback,.fv-form-foundation5.fv-form-horizontal [type=radio]~.fv-control-feedback{top:-8px}.fv-form-foundation5 label .fv-control-feedback{top:21px}.fv-form-foundation5 [type=checkbox]~.fv-control-feedback,.fv-form-foundation5 [type=radio]~.fv-control-feedback{top:15px}.fv-form-foundation5 .error .fv-control-feedback{color:#f04124}.fv-form-foundation5 .fv-has-success .fv-control-feedback,.fv-form-foundation5 .fv-has-success label{color:#43AC6A}.fv-form-foundation .fv-control-feedback{right:15px;width:39px;height:39px;line-height:39px}.fv-form-foundation [type=checkbox]~.fv-control-feedback,.fv-form-foundation [type=radio]~.fv-control-feedback{top:-7px}.fv-form-foundation fieldset [type=checkbox]~.fv-control-feedback,.fv-form-foundation fieldset [type=radio]~.fv-control-feedback,.fv-form-foundation label .fv-control-feedback{top:25px}.fv-form-foundation .fv-has-error .fv-control-feedback,.fv-form-foundation .fv-has-error fieldset legend,.fv-form-foundation .fv-has-error label{color:#ec5840}.fv-form-foundation .fv-has-success .fv-control-feedback,.fv-form-foundation .fv-has-success fieldset legend,.fv-form-foundation .fv-has-success label{color:#3adb76}.fv-foundation-tooltip:before{border-color:transparent transparent #0a0a0a}.fv-form-pure .fv-control-feedback{width:36px;height:36px;line-height:36px}.fv-form-pure.pure-form-aligned [type=checkbox]~.fv-control-feedback,.fv-form-pure.pure-form-aligned [type=radio]~.fv-control-feedback{top:-9px}.fv-form-pure.pure-form-aligned .fv-help-block{margin-top:5px;margin-left:180px}.fv-form-pure.pure-form-stacked .pure-control-group{margin-bottom:8px}.fv-form-pure.pure-form-stacked .fv-control-feedback{top:22px}.fv-form-pure.pure-form-stacked .pure-radio~.fv-control-feedback{top:20px}.fv-form-pure.pure-form-stacked .fv-sr-only~.fv-control-feedback{top:-9px}.fv-form-pure .fv-has-error .fv-control-feedback,.fv-form-pure .fv-has-error .fv-help-block,.fv-form-pure .fv-has-error label{color:#CA3C3C}.fv-form-pure .fv-has-success .fv-control-feedback,.fv-form-pure .fv-has-success .fv-help-block,.fv-form-pure .fv-has-success label{color:#1CB841}.fv-form-semantic .error .icon,.fv-form-semantic.ui.form .fields.error label{color:#9f3a38}.fv-form-semantic .fv-control-feedback{right:7px}.fv-form-uikit .fv-control-feedback{top:25px;width:30px;height:30px;line-height:30px}.fv-form-uikit.uk-form-horizontal .fv-control-feedback{top:0}.fv-form-uikit.uk-form-horizontal [type=checkbox]~.fv-control-feedback,.fv-form-uikit.uk-form-horizontal [type=radio]~.fv-control-feedback{top:-7px}.fv-form-uikit label.fv-sr-only~.fv-control-feedback{top:-7px!important}.fv-form-uikit.uk-form-stacked [type=checkbox]~.fv-control-feedback,.fv-form-uikit.uk-form-stacked [type=radio]~.fv-control-feedback{top:20px}.fv-form-uikit .fv-has-error .fv-control-feedback,.fv-form-uikit .fv-has-error .uk-form-label,.fv-form-uikit .fv-has-error label{color:#D85030}.fv-form-uikit .fv-has-success .fv-control-feedback,.fv-form-uikit .fv-has-success .uk-form-label,.fv-form-uikit .fv-has-success label{color:#659F13} 2 | .hll { background-color: #ffffcc } 3 | .c { color: #999988; font-style: italic } /* Comment */ 4 | .err { color: #a61717; background-color: #e3d2d2 } /* Error */ 5 | .k { color: #000000; font-weight: bold } /* Keyword */ 6 | .o { color: #000000; font-weight: bold } /* Operator */ 7 | .cm { color: #999988; font-style: italic } /* Comment.Multiline */ 8 | .cp { color: #999999; font-weight: bold; font-style: italic } /* Comment.Preproc */ 9 | .c1 { color: #999988; font-style: italic } /* Comment.Single */ 10 | .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ 11 | .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ 12 | .ge { color: #000000; font-style: italic } /* Generic.Emph */ 13 | .gr { color: #aa0000 } /* Generic.Error */ 14 | .gh { color: #999999 } /* Generic.Heading */ 15 | .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ 16 | .go { color: #888888 } /* Generic.Output */ 17 | .gp { color: #555555 } /* Generic.Prompt */ 18 | .gs { font-weight: bold } /* Generic.Strong */ 19 | .gu { color: #aaaaaa } /* Generic.Subheading */ 20 | .gt { color: #aa0000 } /* Generic.Traceback */ 21 | .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ 22 | .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ 23 | .kn { color: #000000; font-weight: bold } /* Keyword.Namespace */ 24 | .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ 25 | .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ 26 | .kt { color: #445588; font-weight: bold } /* Keyword.Type */ 27 | .m { color: #009999 } /* Literal.Number */ 28 | .s { color: #d01040 } /* Literal.String */ 29 | .na { color: #008080 } /* Name.Attribute */ 30 | .nb { color: #0086B3 } /* Name.Builtin */ 31 | .nc { color: #445588; font-weight: bold } /* Name.Class */ 32 | .no { color: #008080 } /* Name.Constant */ 33 | .nd { color: #3c5d5d; font-weight: bold } /* Name.Decorator */ 34 | .ni { color: #800080 } /* Name.Entity */ 35 | .ne { color: #990000; font-weight: bold } /* Name.Exception */ 36 | .nf { color: #990000; font-weight: bold } /* Name.Function */ 37 | .nl { color: #990000; font-weight: bold } /* Name.Label */ 38 | .nn { color: #555555 } /* Name.Namespace */ 39 | .nt { color: #000080 } /* Name.Tag */ 40 | .nv { color: #008080 } /* Name.Variable */ 41 | .ow { color: #000000; font-weight: bold } /* Operator.Word */ 42 | .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .mf { color: #009999 } /* Literal.Number.Float */ 44 | .mh { color: #009999 } /* Literal.Number.Hex */ 45 | .mi { color: #009999 } /* Literal.Number.Integer */ 46 | .mo { color: #009999 } /* Literal.Number.Oct */ 47 | .sb { color: #d01040 } /* Literal.String.Backtick */ 48 | .sc { color: #d01040 } /* Literal.String.Char */ 49 | .sd { color: #d01040 } /* Literal.String.Doc */ 50 | .s2 { color: #d01040 } /* Literal.String.Double */ 51 | .se { color: #d01040 } /* Literal.String.Escape */ 52 | .sh { color: #d01040 } /* Literal.String.Heredoc */ 53 | .si { color: #d01040 } /* Literal.String.Interpol */ 54 | .sx { color: #d01040 } /* Literal.String.Other */ 55 | .sr { color: #009926 } /* Literal.String.Regex */ 56 | .s1 { color: #d01040 } /* Literal.String.Single */ 57 | .ss { color: #990073 } /* Literal.String.Symbol */ 58 | .bp { color: #999999 } /* Name.Builtin.Pseudo */ 59 | .vc { color: #008080 } /* Name.Variable.Class */ 60 | .vg { color: #008080 } /* Name.Variable.Global */ 61 | .vi { color: #008080 } /* Name.Variable.Instance */ 62 | .il { color: #009999 } /* Literal.Number.Integer.Long */ 63 | 64 | /* --- Fonts --- */ 65 | @font-face { 66 | font-family: 'Open Sans'; 67 | font-weight: 400; 68 | font-style: normal; 69 | src: url('http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3fY6323mHUZFJMgTvxaG2iE.eot'); 70 | src: url('http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3fY6323mHUZFJMgTvxaG2iE.eot?#iefix') format('embedded-opentype'), local('Open Sans'), local('Open-Sans-regular'), url('http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3ZBw1xU1rKptJj_0jans920.woff2') format('woff2'), url('http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3bO3LdcAZYWl9Si6vvxL-qU.woff') format('woff'), url('http://fonts.gstatic.com/s/opensans/v13/cJZKeOuBrn4kERxqtaUH3aCWcynf_cDxXwCLxiixG1c.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=cJZKeOuBrn4kERxqtaUH3Zbd9NUM7myrQQz30yPaGQ4&skey=62c1cbfccc78b4b2&v=v13#OpenSans') format('svg'); 71 | } 72 | @font-face { 73 | font-family: 'Open Sans'; 74 | font-weight: 700; 75 | font-style: normal; 76 | src: url('http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzHZ2MAKAc2x4R1uOSeegc5U.eot'); 77 | src: url('http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzHZ2MAKAc2x4R1uOSeegc5U.eot?#iefix') format('embedded-opentype'), local('Open Sans Bold'), local('Open-Sans-700'), url('http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzBampu5_7CjHW5spxoeN3Vs.woff2') format('woff2'), url('http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzKRDOzjiPcYnFooOUGCOsRk.woff') format('woff'), url('http://fonts.gstatic.com/s/opensans/v13/k3k702ZOKiLJc3WVjuplzInF5uFdDttMLvmWuJdhhgs.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=k3k702ZOKiLJc3WVjuplzFlIn5tFQcqMuf-jhyJP0ps&skey=cd9e1a36bb25a3c3&v=v13#OpenSans') format('svg'); 78 | } 79 | @font-face { 80 | font-family: 'Open Sans'; 81 | font-weight: 300; 82 | font-style: normal; 83 | src: url('http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTXZ2MAKAc2x4R1uOSeegc5U.eot'); 84 | src: url('http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTXZ2MAKAc2x4R1uOSeegc5U.eot?#iefix') format('embedded-opentype'), local('Open Sans Light'), local('Open-Sans-300'), url('http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTRampu5_7CjHW5spxoeN3Vs.woff2') format('woff2'), url('http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTaRDOzjiPcYnFooOUGCOsRk.woff') format('woff'), url('http://fonts.gstatic.com/s/opensans/v13/DXI1ORHCpsQm3Vp6mXoaTYnF5uFdDttMLvmWuJdhhgs.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=DXI1ORHCpsQm3Vp6mXoaTVlIn5tFQcqMuf-jhyJP0ps&skey=b33cc031a589c168&v=v13#OpenSans') format('svg'); 85 | } 86 | @font-face { 87 | font-family: 'Lato'; 88 | font-weight: 700; 89 | font-style: normal; 90 | src: url('http://fonts.gstatic.com/s/lato/v11/sBtfDPlEIwvKKU53nAG7AQ.eot'); 91 | src: url('http://fonts.gstatic.com/s/lato/v11/sBtfDPlEIwvKKU53nAG7AQ.eot?#iefix') format('embedded-opentype'), local('Lato Bold'), local('Lato-700'), url('http://fonts.gstatic.com/s/lato/v11/MgNNr5y1C_tIEuLEmicLmwLUuEpTyoUstqEm5AMlJo4.woff2') format('woff2'), url('http://fonts.gstatic.com/s/lato/v11/qdgUG4U09HnJwhYI-uK18wLUuEpTyoUstqEm5AMlJo4.woff') format('woff'), url('http://fonts.gstatic.com/s/lato/v11/DvlFBScY1r-FMtZSYIYoYw.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=H4oiIt_Ug9TU5ast0nUT-w&skey=3480a19627739c0d&v=v11#Lato') format('svg'); 92 | } 93 | @font-face { 94 | font-family: 'Lato'; 95 | font-weight: 300; 96 | font-style: normal; 97 | src: url('http://fonts.gstatic.com/s/lato/v11/zLhfkPOm_5ykmdm-wXaiuw.eot'); 98 | src: url('http://fonts.gstatic.com/s/lato/v11/zLhfkPOm_5ykmdm-wXaiuw.eot?#iefix') format('embedded-opentype'), local('Lato Light'), local('Lato-300'), url('http://fonts.gstatic.com/s/lato/v11/22JRxvfANxSmnAhzbFH8PgLUuEpTyoUstqEm5AMlJo4.woff2') format('woff2'), url('http://fonts.gstatic.com/s/lato/v11/kcf5uOXucLcbFOydGU24WALUuEpTyoUstqEm5AMlJo4.woff') format('woff'), url('http://fonts.gstatic.com/s/lato/v11/nj47mAZe0mYUIySgfn0wpQ.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=7yKIrlBXX_AXuUv3Ts9_8g&skey=91f32e07d083dd3a&v=v11#Lato') format('svg'); 99 | } 100 | @font-face { 101 | font-family: 'Lato'; 102 | font-weight: 400; 103 | font-style: normal; 104 | src: url('http://fonts.gstatic.com/s/lato/v11/nQhiC-wSiJx0pvEuJl8d8A.eot'); 105 | src: url('http://fonts.gstatic.com/s/lato/v11/nQhiC-wSiJx0pvEuJl8d8A.eot?#iefix') format('embedded-opentype'), local('Lato Regular'), local('Lato-regular'), url('http://fonts.gstatic.com/s/lato/v11/MDadn8DQ_3oT6kvnUq_2r_esZW2xOQ-xsNqO47m55DA.woff2') format('woff2'), url('http://fonts.gstatic.com/s/lato/v11/qIIYRU-oROkIk8vfvxw6QvesZW2xOQ-xsNqO47m55DA.woff') format('woff'), url('http://fonts.gstatic.com/s/lato/v11/v0SdcGFAl2aezM9Vq_aFTQ.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=hLECvlEj3pKlnS4NFs8NQw&skey=2d58b92a99e1c086&v=v11#Lato') format('svg'); 106 | } 107 | @font-face { 108 | font-family: 'Cardo'; 109 | font-weight: 400; 110 | font-style: normal; 111 | src: url('http://fonts.gstatic.com/s/cardo/v8/WLga5RBcBwOgQwbtYXsULg.eot'); 112 | src: url('http://fonts.gstatic.com/s/cardo/v8/WLga5RBcBwOgQwbtYXsULg.eot?#iefix') format('embedded-opentype'), local('Cardo'), local('Cardo-regular'), url('http://fonts.gstatic.com/s/cardo/v8/f9GbO0_LnwwuaRC6yAh0JKCWcynf_cDxXwCLxiixG1c.woff2') format('woff2'), url('http://fonts.gstatic.com/s/cardo/v8/c6Zi_ulq7hv-avk-G9Yut6CWcynf_cDxXwCLxiixG1c.woff') format('woff'), url('http://fonts.gstatic.com/s/cardo/v8/vjI-nu0OQ4awNWMtISxB9w.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=tJfhmb6XM6Nefrs-MUPqRA&skey=6e473b3c3fa37289&v=v8#Cardo') format('svg'); 113 | } 114 | @font-face { 115 | font-family: 'Cardo'; 116 | font-weight: 700; 117 | font-style: normal; 118 | src: url('http://fonts.gstatic.com/s/cardo/v8/0nnlDTzzEQXukObx6f9ifPesZW2xOQ-xsNqO47m55DA.eot'); 119 | src: url('http://fonts.gstatic.com/s/cardo/v8/0nnlDTzzEQXukObx6f9ifPesZW2xOQ-xsNqO47m55DA.eot?#iefix') format('embedded-opentype'), local('Cardo Bold'), local('Cardo-700'), url('http://fonts.gstatic.com/s/cardo/v8/X-1BEHTKpRYzad3JEXy9-nYhjbSpvc47ee6xR_80Hnw.woff2') format('woff2'), url('http://fonts.gstatic.com/s/cardo/v8/gHC1KgRPdVNdvvVcxLMCY3YhjbSpvc47ee6xR_80Hnw.woff') format('woff'), url('http://fonts.gstatic.com/s/cardo/v8/V5fEf2Hmfq3L3OwPiowl-vesZW2xOQ-xsNqO47m55DA.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=f8uFIJ6wVLbxDxi8rLJmtvesZW2xOQ-xsNqO47m55DA&skey=90e31575a38858c1&v=v8#Cardo') format('svg'); 120 | } 121 | @font-face { 122 | font-family: 'Cardo'; 123 | font-weight: 400; 124 | font-style: italic; 125 | src: url('http://fonts.gstatic.com/s/cardo/v8/RxE5bFgPTBlMPY4Uzh64YQ.eot'); 126 | src: url('http://fonts.gstatic.com/s/cardo/v8/RxE5bFgPTBlMPY4Uzh64YQ.eot?#iefix') format('embedded-opentype'), local('Cardo Italic'), local('Cardo-italic'), url('http://fonts.gstatic.com/s/cardo/v8/aRpKelDgx13ov6asvC3QbgLUuEpTyoUstqEm5AMlJo4.woff2') format('woff2'), url('http://fonts.gstatic.com/s/cardo/v8/mSKSxAIybPTfRoik7xAeTQLUuEpTyoUstqEm5AMlJo4.woff') format('woff'), url('http://fonts.gstatic.com/s/cardo/v8/GRTkKQvSwrQKMUJtWZsm7w.ttf') format('truetype'), url('http://fonts.gstatic.com/l/font?kit=XI1j6UEaGnfkBOdFUUjoUg&skey=26bb5df469a255da&v=v8#Cardo') format('svg'); 127 | } 128 | /* --- Variables --- */ 129 | /* --- Reset --- */ 130 | a, 131 | a:hover, 132 | a:focus { 133 | text-decoration: none; 134 | } 135 | ul { 136 | margin: 0; 137 | padding: 0; 138 | } 139 | pre, 140 | .hljs { 141 | background: transparent; 142 | } 143 | .hll { 144 | background-color: #ffffcc; 145 | display: block; 146 | width: 100%; 147 | } 148 | body { 149 | font-family: 'open sans'; 150 | } 151 | h1, 152 | h2, 153 | h3, 154 | h4 { 155 | font-family: 'Lato'; 156 | } 157 | /* --- Ads --- */ 158 | .doc-ad h6 { 159 | margin-top: 0; 160 | font-weight: normal; 161 | } 162 | /* --- Helpers --- */ 163 | .doc-hr { 164 | background: #c5c5c5; 165 | height: 1px; 166 | margin: 30px auto; 167 | width: 50%; 168 | } 169 | .doc-hr-short { 170 | background: #e5e5e5; 171 | height: 1px; 172 | margin: 30px auto; 173 | width: 30%; 174 | } 175 | .doc-notification { 176 | /*background-color: #ecf0f1;*/ 177 | border-bottom: 1px solid #eee; 178 | height: 50px; 179 | line-height: 50px; 180 | text-align: center; 181 | } 182 | @media (max-width: 767px) { 183 | .doc-notification { 184 | display: none; 185 | } 186 | } 187 | .doc-sample-list { 188 | list-style: none; 189 | margin: 0; 190 | padding: 0; 191 | } 192 | .doc-sample-list li { 193 | position: relative; 194 | } 195 | .doc-sample-list li i { 196 | position: absolute; 197 | right: 0; 198 | } 199 | .doc-sample-error a, 200 | .doc-sample-error i { 201 | color: #a94442; 202 | } 203 | .doc-share-button { 204 | margin: 0 auto; 205 | text-align: center; 206 | } 207 | .doc-share-button label { 208 | background: transparent !important; 209 | } 210 | /* --- Header --- */ 211 | .doc-nav-shown .doc-nav { 212 | background-color: #191919; 213 | display: block; 214 | width: 280px; 215 | z-index: 1; 216 | } 217 | .doc-nav-shown .doc-nav-toggle { 218 | left: 280px; 219 | } 220 | .doc-nav-shown .doc-content-wrapper { 221 | margin-left: 280px; 222 | } 223 | .doc-nav { 224 | bottom: 0; 225 | height: 100%; 226 | display: none; 227 | overflow: scroll; 228 | position: fixed; 229 | top: 0; 230 | left: 0; 231 | } 232 | .doc-nav ul { 233 | padding-top: 10px; 234 | text-align: right; 235 | text-transform: uppercase; 236 | } 237 | .doc-nav li { 238 | list-style-type: none; 239 | padding-right: 20px; 240 | } 241 | .doc-nav li a { 242 | color: #fff; 243 | display: block; 244 | font-size: 15px; 245 | padding: 0 0 0 20px; 246 | height: 32px; 247 | line-height: 32px; 248 | } 249 | .doc-nav li.doc-nav-active { 250 | border-left: 3px solid #3498db; 251 | } 252 | .doc-nav li i { 253 | border: 1px solid #ccc; 254 | border-radius: 15px; 255 | width: 30px; 256 | height: 30px; 257 | line-height: 30px; 258 | margin-left: 10px; 259 | text-align: center; 260 | } 261 | .doc-nav li span { 262 | background-color: #CF0000; 263 | border-radius: 4px; 264 | display: inline-block; 265 | margin-right: 5px; 266 | width: 8px; 267 | height: 8px; 268 | } 269 | .doc-nav .doc-hr { 270 | background: #7f8c8d; 271 | margin: 5px auto; 272 | } 273 | .doc-creator { 274 | color: #fff; 275 | margin-top: 20px; 276 | text-align: center; 277 | } 278 | .doc-creator a { 279 | color: #fff; 280 | } 281 | .doc-creator i { 282 | color: #c0392b; 283 | } 284 | .doc-creator img { 285 | border-radius: 30px; 286 | width: 60px; 287 | height: 60px; 288 | margin: 10px 0; 289 | } 290 | .doc-creator .btn { 291 | background: #3498db; 292 | text-transform: uppercase; 293 | } 294 | .doc-creator .btn:hover { 295 | color: #fff; 296 | } 297 | .doc-nav-toggle { 298 | background: #3498db; 299 | color: #fff; 300 | cursor: pointer; 301 | display: block; 302 | font-size: 20px; 303 | line-height: 50px; 304 | position: fixed; 305 | left: 0; 306 | top: 0; 307 | text-align: center; 308 | z-index: 1000; 309 | width: 50px; 310 | height: 50px; 311 | } 312 | .doc-nav-toggle:hover { 313 | color: #fff; 314 | } 315 | /* --- Footer --- */ 316 | .doc-footer { 317 | border-top: 1px solid #ecf0f1; 318 | padding: 50px 0; 319 | position: relative; 320 | text-align: center; 321 | } 322 | .doc-footer p { 323 | margin-bottom: 15px; 324 | } 325 | .doc-footer .btn { 326 | background: #3498db; 327 | color: #fff; 328 | font-size: 20px; 329 | margin: 5px 15px; 330 | } 331 | .doc-footer .btn:hover { 332 | color: #fff; 333 | } 334 | /* --- Home --- */ 335 | .doc-center-wrapper { 336 | text-align: center; 337 | } 338 | .doc-center-wrapper:before { 339 | content: ''; 340 | display: inline-block; 341 | height: 100%; 342 | vertical-align: middle; 343 | } 344 | .doc-center-container { 345 | display: inline-block; 346 | vertical-align: middle; 347 | } 348 | .doc-home-counter { 349 | color: #3498db; 350 | } 351 | .doc-home-intro { 352 | background: url('/asset/img/pattern.png') top left repeat; 353 | color: #fff; 354 | padding-bottom: 20px; 355 | } 356 | .doc-home-intro h1 { 357 | font-size: 40px; 358 | line-height: 1.2; 359 | margin: 60px 0 30px 0; 360 | } 361 | .doc-home-intro h1 a { 362 | color: #f1c40f; 363 | } 364 | .doc-home-intro a { 365 | color: #ccc; 366 | } 367 | .doc-home-intro p { 368 | font-size: 80%; 369 | position: absolute; 370 | left: 0; 371 | bottom: 0; 372 | text-align: center; 373 | width: 100%; 374 | } 375 | .doc-home-feature h2 { 376 | font-size: 40px; 377 | margin-bottom: 30px; 378 | } 379 | .doc-home-feature .col-sm-4 { 380 | margin-bottom: 40px; 381 | } 382 | .doc-home-feature i { 383 | background: #bdc3c7; 384 | border-radius: 60px; 385 | color: #ecf0f1; 386 | font-size: 60px; 387 | line-height: 120px; 388 | height: 120px; 389 | width: 120px; 390 | } 391 | .doc-home-feature h4 { 392 | font-size: 30px; 393 | text-transform: uppercase; 394 | } 395 | .doc-home-feature p { 396 | margin: 0 auto; 397 | width: 75%; 398 | } 399 | .doc-home-feature .btn { 400 | background: #3498db; 401 | color: #fff; 402 | font-size: 20px; 403 | margin-bottom: 15px; 404 | text-transform: uppercase; 405 | } 406 | .doc-home-feature .btn:hover { 407 | color: #fff; 408 | } 409 | .doc-home-tweet h2 { 410 | font-size: 40px; 411 | margin-bottom: 30px; 412 | } 413 | .doc-home-tweet .col-sm-4 { 414 | margin-bottom: 10px; 415 | position: relative; 416 | text-align: center; 417 | } 418 | .doc-home-tweet .col-sm-4 img { 419 | border: 1px solid #ccc; 420 | border-radius: 30px; 421 | width: 60px; 422 | height: 60px; 423 | padding: 5px; 424 | position: absolute; 425 | top: 0; 426 | left: 20px; 427 | } 428 | .doc-home-tweet .col-sm-4 blockquote { 429 | border-left: none; 430 | font-size: 14px; 431 | margin: 0 0 10px 80px; 432 | padding: 0; 433 | text-align: left; 434 | } 435 | .doc-home-tweet .col-sm-4 span { 436 | display: block; 437 | font-size: 12px; 438 | text-align: right; 439 | } 440 | .doc-home-tweet p { 441 | margin-top: 20px; 442 | } 443 | .doc-home-tweet li { 444 | list-style-type: none; 445 | display: inline-block; 446 | } 447 | .doc-home-tweet li a { 448 | color: #fff; 449 | font-size: 25px; 450 | } 451 | .doc-home-tweet li i { 452 | background: #3498db; 453 | border-radius: 25px; 454 | width: 50px; 455 | height: 50px; 456 | line-height: 50px; 457 | } 458 | .doc-home-tweet .btn { 459 | background: #3498db; 460 | color: #fff; 461 | font-size: 20px; 462 | margin-bottom: 15px; 463 | text-transform: uppercase; 464 | } 465 | .doc-home-tweet .btn:hover { 466 | color: #fff; 467 | } 468 | .doc-home-download h3 { 469 | font-size: 80px; 470 | margin-bottom: 20px; 471 | } 472 | .doc-home-download h3 span { 473 | background: #000; 474 | border-radius: 8px; 475 | color: #fff; 476 | padding: 0 5px; 477 | margin: 0 5px; 478 | } 479 | .doc-home-download .btn { 480 | background: #3498db; 481 | color: #fff; 482 | font-size: 20px; 483 | margin: 5px; 484 | text-transform: uppercase; 485 | } 486 | .doc-home-download .btn:hover { 487 | color: #fff; 488 | } 489 | @media (max-width: 767px) { 490 | .doc-center-wrapper:before { 491 | height: auto; 492 | } 493 | .onepage-wrapper .section { 494 | height: auto !important; 495 | } 496 | .doc-home-intro h3 { 497 | position: static; 498 | } 499 | } 500 | /* --- Download --- */ 501 | .doc-download-quote .col-sm-4 { 502 | margin: 15px 0; 503 | } 504 | .doc-download-quote p { 505 | text-align: center; 506 | } 507 | .doc-download-quote img { 508 | border: 1px solid #ccc; 509 | border-radius: 30px; 510 | width: 60px; 511 | height: 60px; 512 | padding: 5px; 513 | } 514 | .doc-download-quote blockquote { 515 | border-left: none; 516 | font-size: 14px; 517 | margin: 0 0 5px; 518 | padding: 0; 519 | } 520 | .doc-download-quote span { 521 | display: block; 522 | font-size: 12px; 523 | text-align: right; 524 | } 525 | .doc-plan .btn { 526 | background: #3498db; 527 | color: #fff; 528 | margin: 5px; 529 | text-transform: uppercase; 530 | } 531 | .doc-plan .btn:hover { 532 | color: #fff; 533 | } 534 | .doc-sellfy-button { 535 | background-image: url('/asset/img/sellfy-button.png'); 536 | display: inline-block; 537 | width: 162px; 538 | height: 32px; 539 | } 540 | .doc-sellfy-button span { 541 | display: block; 542 | text-align: center; 543 | line-height: 32px; 544 | color: #595959; 545 | font-weight: 600; 546 | font-size: 13px; 547 | padding-left: 96px; 548 | } 549 | .doc-gumroad-button { 550 | background: white url('/asset/img/gumroad-button-bar.jpg') repeat-x; 551 | border-radius: 4px; 552 | box-shadow: rgba(0, 0, 0, 0.4) 0 0 2px; 553 | color: #999; 554 | display: inline-block; 555 | font-style: normal; 556 | line-height: 50px; 557 | padding: 0 15px; 558 | text-shadow: none; 559 | text-decoration: none; 560 | } 561 | .doc-gumroad-button span { 562 | background-image: url('/asset/img/gumroad-button-logo.png'); 563 | background-size: cover; 564 | height: 17px; 565 | width: 16px; 566 | display: inline-block; 567 | margin-bottom: -3px; 568 | margin-right: 15px; 569 | } 570 | /* --- Page --- */ 571 | .doc-content-container { 572 | padding-bottom: 30px; 573 | } 574 | .doc-content-container h2 { 575 | font-weight: bold; 576 | padding-bottom: 15px; 577 | } 578 | .doc-content-container h5 { 579 | font-weight: bold; 580 | } 581 | .doc-content-container code { 582 | background: transparent; 583 | } 584 | .doc-content-container pre { 585 | word-wrap: normal; 586 | } 587 | .doc-content-container pre code { 588 | white-space: pre; 589 | } 590 | .doc-list { 591 | margin: 10px 0 10px 15px; 592 | } 593 | .doc-alert { 594 | border-left: 5px solid #1ABC9C; 595 | font-family: 'cardo'; 596 | font-size: 18px; 597 | font-style: italic; 598 | margin: 20px 0; 599 | padding-left: 15px; 600 | } 601 | .doc-alert.doc-alert-warning { 602 | border-left-color: #c0392b; 603 | } 604 | .doc-alert.doc-alert-info { 605 | border-left-color: #1ABC9C; 606 | } 607 | .doc-heading { 608 | border-bottom: 1px solid #ecf0f1; 609 | margin-bottom: 30px; 610 | padding-bottom: 30px; 611 | text-align: center; 612 | } 613 | .doc-heading h1 { 614 | font-size: 50px; 615 | font-weight: 700; 616 | padding: 50px 0 20px 0; 617 | } 618 | .doc-edit-btn { 619 | background: #3498db; 620 | color: #fff; 621 | font-size: 20px; 622 | margin-bottom: 15px; 623 | } 624 | .doc-edit-btn:hover { 625 | color: #fff; 626 | } 627 | .doc-demo { 628 | margin-bottom: 15px; 629 | } 630 | .doc-demo .doc-demo-direct { 631 | float: right; 632 | display: none; 633 | } 634 | .doc-demo .tab-pane { 635 | padding: 10px 15px; 636 | } 637 | .doc-demo .tab-content > .active { 638 | border: 1px solid #ddd; 639 | border-bottom-left-radius: 4px; 640 | border-bottom-right-radius: 4px; 641 | border-top: none; 642 | } 643 | .doc-demo pre { 644 | border: none; 645 | margin: 0; 646 | padding: 0; 647 | } 648 | .doc-demo li:before { 649 | content: ''; 650 | } 651 | .doc-demo-frame { 652 | border: none; 653 | display: none; 654 | width: 100%; 655 | } 656 | .doc-demo-loader { 657 | line-height: 100px; 658 | width: 100%; 659 | height: 100px; 660 | text-align: center; 661 | } 662 | /* --- TOC --- */ 663 | .doc-content-container h2, 664 | .doc-content-container h3, 665 | .doc-content-container h4, 666 | .doc-content-container h5, 667 | .doc-content-container h6 { 668 | position: relative; 669 | } 670 | .doc-content-container h2 .toc-anchor, 671 | .doc-content-container h3 .toc-anchor, 672 | .doc-content-container h4 .toc-anchor, 673 | .doc-content-container h5 .toc-anchor, 674 | .doc-content-container h6 .toc-anchor { 675 | position: absolute; 676 | top: 0; 677 | right: 0; 678 | } 679 | .doc-content-container h4.modal-title { 680 | position: inherit; 681 | } 682 | .doc-toc { 683 | margin: 20px 0; 684 | } 685 | .doc-toc .nav > .active > a, 686 | .doc-toc .nav > .active:hover > a, 687 | .doc-toc .nav > .active :focus > a { 688 | border-left: 2px solid #3498db; 689 | } 690 | .doc-toc .nav > .active > ul { 691 | display: block; 692 | } 693 | .doc-toc .nav > li > a { 694 | padding-top: 4px; 695 | padding-bottom: 4px; 696 | } 697 | .doc-toc .nav .nav > li > a { 698 | font-size: 90%; 699 | } 700 | .doc-toc a.toc-link-2 { 701 | padding-left: 25px; 702 | } 703 | .doc-toc .toc-link-3 { 704 | padding-left: 50px; 705 | } 706 | .doc-toc .toc-link-4 { 707 | padding-left: 75px; 708 | } 709 | @media (min-width: 992px) { 710 | .doc-toc.affix { 711 | position: fixed; 712 | top: 0; 713 | } 714 | .doc-toc.affix, 715 | .doc-toc.affix-bottom { 716 | width: 213px; 717 | } 718 | } 719 | @media (min-width: 1200px) { 720 | .doc-toc.affix, 721 | .doc-toc.affix-bottom { 722 | width: 263px; 723 | } 724 | } 725 | /* --- Post --- */ 726 | .doc-post h2 { 727 | text-align: center; 728 | } 729 | .doc-post-meta { 730 | text-align: center; 731 | } 732 | .doc-post-meta img { 733 | border-radius: 40px; 734 | width: 80px; 735 | height: 80px; 736 | margin-bottom: 10px; 737 | } 738 | /* --- Pagination --- */ 739 | .doc-pagination { 740 | float: left; 741 | margin: 30px 0; 742 | width: 100%; 743 | } 744 | .doc-pagination a, 745 | .doc-pagination span { 746 | display: block; 747 | float: left; 748 | font-size: 18px; 749 | line-height: 45px; 750 | width: 50%; 751 | height: 45px; 752 | text-align: center; 753 | } 754 | .doc-pagination span { 755 | color: #bfbfbf; 756 | } 757 | .doc-pagination a:hover { 758 | background-color: #44b1f6; 759 | text-decoration: none; 760 | color: #fff; 761 | border-color: #44b1f6; 762 | } 763 | .doc-pagination .doc-pagination-older { 764 | border-top-left-radius: 5px; 765 | border-bottom-left-radius: 5px; 766 | border-top: 1px solid #e5e5e5; 767 | border-left: 1px solid #e5e5e5; 768 | border-bottom: 1px solid #e5e5e5; 769 | } 770 | .doc-pagination .doc-pagination-newer { 771 | border-top-right-radius: 5px; 772 | border-bottom-right-radius: 5px; 773 | border: 1px solid #e5e5e5; 774 | } 775 | /* --- Addon --- */ 776 | .doc-addon { 777 | border: 1px solid #d5d5d5; 778 | border-bottom-left-radius: 4px; 779 | border-bottom-right-radius: 4px; 780 | margin-bottom: 15px; 781 | } 782 | .doc-addon-image { 783 | border-bottom: 1px solid #e5e5e5; 784 | } 785 | .doc-addon-meta { 786 | padding: 10px; 787 | } 788 | .doc-addon-meta h2 { 789 | margin: 5px 0; 790 | } 791 | /* --- Heart --- */ 792 | .doc-heart { 793 | color: #c0392b; 794 | -webkit-animation: heart-beat 1s linear infinite; 795 | -moz-animation: heart-beat 1s linear infinite; 796 | -ms-animation: heart-beat 1s linear infinite; 797 | animation: heart-beat 1s linear infinite; 798 | } 799 | @keyframes heart-beat { 800 | 0% { 801 | -webkit-transform: scale(1.1); 802 | -moz-transform: scale(1.1); 803 | -o-transform: scale(1.1); 804 | -ms-transform: scale(1.1); 805 | transform: scale(1.1); 806 | } 807 | 50% { 808 | -webkit-transform: scale(0.8); 809 | -moz-transform: scale(0.8); 810 | -o-transform: scale(0.8); 811 | -ms-transform: scale(0.8); 812 | transform: scale(0.8); 813 | } 814 | 100% { 815 | -webkit-transform: scale(1); 816 | -moz-transform: scale(1); 817 | -o-transform: scale(1); 818 | -ms-transform: scale(1); 819 | transform: scale(1); 820 | } 821 | } 822 | @-moz-keyframes heart-beat { 823 | 0% { 824 | -moz-transform: scale(1.1); 825 | transform: scale(1.1); 826 | } 827 | 50% { 828 | -moz-transform: scale(0.8); 829 | transform: scale(0.8); 830 | } 831 | 100% { 832 | -moz-transform: scale(1); 833 | transform: scale(1); 834 | } 835 | } 836 | @-webkit-keyframes heart-beat { 837 | 0% { 838 | -webkit-transform: scale(1.1); 839 | transform: scale(1.1); 840 | } 841 | 50% { 842 | -webkit-transform: scale(0.8); 843 | transform: scale(0.8); 844 | } 845 | 100% { 846 | -webkit-transform: scale(1); 847 | transform: scale(1); 848 | } 849 | } 850 | @-ms-keyframes heart-beat { 851 | 0% { 852 | -ms-transform: scale(1.1); 853 | transform: scale(1.1); 854 | } 855 | 50% { 856 | -ms-transform: scale(0.8); 857 | transform: scale(0.8); 858 | } 859 | 100% { 860 | -ms-transform: scale(1); 861 | transform: scale(1); 862 | } 863 | } 864 | -------------------------------------------------------------------------------- /user-account-registration/src/main/resources/static/js/fv.min.js: -------------------------------------------------------------------------------- 1 | if(window.FormValidation={AddOn:{},Framework:{},I18n:{},Validator:{}},"undefined"==typeof jQuery)throw new Error("FormValidation requires jQuery");!function(a){var b=a.fn.jquery.split(" ")[0].split(".");if(+b[0]<2&&+b[1]<9||1===+b[0]&&9===+b[1]&&+b[2]<1)throw new Error("FormValidation requires jQuery version 1.9.1 or higher")}(jQuery),function(a){FormValidation.Base=function(b,c,d){this.$form=a(b),this.options=a.extend({},a.fn.formValidation.DEFAULT_OPTIONS,c),this._namespace=d||"fv",this.$invalidFields=a([]),this.$submitButton=null,this.$hiddenButton=null,this.STATUS_NOT_VALIDATED="NOT_VALIDATED",this.STATUS_VALIDATING="VALIDATING",this.STATUS_INVALID="INVALID",this.STATUS_VALID="VALID",this.STATUS_IGNORED="IGNORED",this.DEFAULT_MESSAGE=a.fn.formValidation.DEFAULT_MESSAGE,this._ieVersion=function(){for(var a=3,b=document.createElement("div"),c=b.all||[];b.innerHTML="",c[0];);return a>4?a:document.documentMode}();var e=document.createElement("div");this._changeEvent=9!==this._ieVersion&&"oninput"in e?"input":"keyup",this._submitIfValid=null,this._cacheFields={},this._init()},FormValidation.Base.prototype={constructor:FormValidation.Base,_exceedThreshold:function(b){var c=this._namespace,d=b.attr("data-"+c+"-field"),e=this.options.fields[d].threshold||this.options.threshold;if(!e)return!0;var f=-1!==a.inArray(b.attr("type"),["button","checkbox","file","hidden","image","radio","reset","submit"]);return f||b.val().length>=e},_init:function(){var b=this,c=this._namespace,d={addOns:{},autoFocus:this.$form.attr("data-"+c+"-autofocus"),button:{selector:this.$form.attr("data-"+c+"-button-selector")||this.$form.attr("data-"+c+"-submitbuttons"),disabled:this.$form.attr("data-"+c+"-button-disabled")},control:{valid:this.$form.attr("data-"+c+"-control-valid"),invalid:this.$form.attr("data-"+c+"-control-invalid")},err:{clazz:this.$form.attr("data-"+c+"-err-clazz"),container:this.$form.attr("data-"+c+"-err-container")||this.$form.attr("data-"+c+"-container"),parent:this.$form.attr("data-"+c+"-err-parent")},events:{formInit:this.$form.attr("data-"+c+"-events-form-init"),formPreValidate:this.$form.attr("data-"+c+"-events-form-prevalidate"),formError:this.$form.attr("data-"+c+"-events-form-error"),formReset:this.$form.attr("data-"+c+"-events-form-reset"),formSuccess:this.$form.attr("data-"+c+"-events-form-success"),fieldAdded:this.$form.attr("data-"+c+"-events-field-added"),fieldRemoved:this.$form.attr("data-"+c+"-events-field-removed"),fieldInit:this.$form.attr("data-"+c+"-events-field-init"),fieldError:this.$form.attr("data-"+c+"-events-field-error"),fieldReset:this.$form.attr("data-"+c+"-events-field-reset"),fieldSuccess:this.$form.attr("data-"+c+"-events-field-success"),fieldStatus:this.$form.attr("data-"+c+"-events-field-status"),localeChanged:this.$form.attr("data-"+c+"-events-locale-changed"),validatorError:this.$form.attr("data-"+c+"-events-validator-error"),validatorSuccess:this.$form.attr("data-"+c+"-events-validator-success"),validatorIgnored:this.$form.attr("data-"+c+"-events-validator-ignored")},excluded:this.$form.attr("data-"+c+"-excluded"),icon:{valid:this.$form.attr("data-"+c+"-icon-valid")||this.$form.attr("data-"+c+"-feedbackicons-valid"),invalid:this.$form.attr("data-"+c+"-icon-invalid")||this.$form.attr("data-"+c+"-feedbackicons-invalid"),validating:this.$form.attr("data-"+c+"-icon-validating")||this.$form.attr("data-"+c+"-feedbackicons-validating"),feedback:this.$form.attr("data-"+c+"-icon-feedback")},live:this.$form.attr("data-"+c+"-live"),locale:this.$form.attr("data-"+c+"-locale"),message:this.$form.attr("data-"+c+"-message"),onPreValidate:this.$form.attr("data-"+c+"-onprevalidate"),onError:this.$form.attr("data-"+c+"-onerror"),onReset:this.$form.attr("data-"+c+"-onreset"),onSuccess:this.$form.attr("data-"+c+"-onsuccess"),row:{selector:this.$form.attr("data-"+c+"-row-selector")||this.$form.attr("data-"+c+"-group"),valid:this.$form.attr("data-"+c+"-row-valid"),invalid:this.$form.attr("data-"+c+"-row-invalid"),feedback:this.$form.attr("data-"+c+"-row-feedback")},threshold:this.$form.attr("data-"+c+"-threshold"),trigger:this.$form.attr("data-"+c+"-trigger"),verbose:this.$form.attr("data-"+c+"-verbose"),fields:{}};this.$form.attr("novalidate","novalidate").addClass(this.options.elementClass).on("submit."+c,function(a){a.preventDefault(),b.validate()}).on("click."+c,this.options.button.selector,function(){b.$submitButton=a(this),b._submitIfValid=!0}),(this.options.declarative===!0||"true"===this.options.declarative)&&this.$form.find("[name], [data-"+c+"-field]").each(function(){var e=a(this),f=e.attr("name")||e.attr("data-"+c+"-field"),g=b._parseOptions(e);g&&(e.attr("data-"+c+"-field",f),d.fields[f]=a.extend({},g,d.fields[f]))}),this.options=a.extend(!0,this.options,d),"string"==typeof this.options.err.parent&&(this.options.err.parent=new RegExp(this.options.err.parent)),this.options.container&&(this.options.err.container=this.options.container,delete this.options.container),this.options.feedbackIcons&&(this.options.icon=a.extend(!0,this.options.icon,this.options.feedbackIcons),delete this.options.feedbackIcons),this.options.group&&(this.options.row.selector=this.options.group,delete this.options.group),this.options.submitButtons&&(this.options.button.selector=this.options.submitButtons,delete this.options.submitButtons),FormValidation.I18n[this.options.locale]||(this.options.locale=a.fn.formValidation.DEFAULT_OPTIONS.locale),(this.options.declarative===!0||"true"===this.options.declarative)&&(this.options=a.extend(!0,this.options,{addOns:this._parseAddOnOptions()})),this.$hiddenButton=a(" 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 93 | 94 | 95 | 96 | 97 | 98 | 167 | }); 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /user-account-registration/src/main/resources/templates/register.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | User Registration with Spring Boot 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | 31 |
32 | 33 |

New User Registration

34 | 35 |
38 | 39 | 40 | 42 | 43 | 46 | 47 | 48 | 51 | 52 | 55 | 56 | 57 | 59 | 60 | 63 | 64 | 65 |
66 | 68 | 69 | 70 | 71 | 72 | 74 | 76 |
77 | 78 |
79 | 80 | 81 | 82 | 84 | 86 |
87 | 88 |
89 | 90 | 91 | 92 | 95 | 97 | 98 |
99 | 100 | 101 | 103 | 104 |
105 |

106 | 107 |

108 |
109 |
110 | 111 | 112 | 113 | 115 | 116 | 117 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /user-account-registration/src/test/java/com/codebyamir/UserAccountRegistrationApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.codebyamir; 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 UserAccountRegistrationApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------