├── .gitattributes ├── .gitignore ├── .travis.yml ├── Procfile ├── README.md ├── backend ├── .gitignore ├── build.gradle.kts ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── kotlinspringvue │ │ │ └── backend │ │ │ ├── BackendApplication.kt │ │ │ ├── config │ │ │ └── WebSecurityConfig.kt │ │ │ ├── controller │ │ │ ├── AuthController.kt │ │ │ └── BackendController.kt │ │ │ ├── email │ │ │ ├── EmailService.kt │ │ │ └── EmailServiceImpl.kt │ │ │ ├── jpa │ │ │ ├── Person.kt │ │ │ ├── Role.kt │ │ │ ├── User.kt │ │ │ └── VerificationToken.kt │ │ │ ├── jwt │ │ │ ├── JwtAuthEntryPoint.kt │ │ │ ├── JwtAuthTokenFilter.kt │ │ │ └── JwtProvider.kt │ │ │ ├── model │ │ │ ├── Greeting.kt │ │ │ ├── LoginUser.kt │ │ │ └── NewUser.kt │ │ │ ├── repository │ │ │ ├── PersonRepository.kt │ │ │ ├── RoleRepository.kt │ │ │ ├── UserRepository.kt │ │ │ └── VerificationTokenRepository.kt │ │ │ ├── service │ │ │ ├── ReCaptchaService.kt │ │ │ ├── UserDetailsService.kt │ │ │ └── UserDetailsServiceImpl.kt │ │ │ └── web │ │ │ ├── error │ │ │ └── UserAlreadyExistException.kt │ │ │ └── response │ │ │ ├── JwtResponse.kt │ │ │ ├── ResponseMessage.kt │ │ │ └── SuccessfulSigninResponse.kt │ └── resources │ │ ├── application.properties │ │ └── templates │ │ └── emailTemplate.html │ └── test │ └── kotlin │ └── com │ └── kotlinspringvue │ └── backend │ └── BackendApplicationTests.kt ├── build.gradle.kts ├── frontend ├── .gitignore ├── babel.config.js ├── build.gradle.kts ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── img │ │ │ ├── kotlin-logo.png │ │ │ ├── spring-boot-logo.png │ │ │ └── vuejs-logo.png │ ├── components │ │ ├── AdminPage.vue │ │ ├── EmailPage.vue │ │ ├── Greeting.vue │ │ ├── HelloWorld.vue │ │ ├── Home.vue │ │ ├── RegistrationConfirmPage.vue │ │ ├── SignIn.vue │ │ ├── SignUp.vue │ │ ├── UserPage.vue │ │ └── http-common.js │ ├── main.js │ ├── router.js │ └── store │ │ └── index.js └── vue.config.js ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Help # 4 | backend/*.md 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # Eclipse # 12 | .settings 13 | .project 14 | .classpath 15 | .studio 16 | target 17 | 18 | # NetBeans # 19 | backend/nbproject/private/ 20 | backend/nbbuild/ 21 | backend/dist/ 22 | backend/nbdist/ 23 | backend/.nb-gradle/ 24 | backend/build/ 25 | 26 | # Apple # 27 | .DS_Store 28 | 29 | # Intellij # 30 | .idea 31 | *.iml 32 | *.log 33 | 34 | # logback 35 | logback.out.xml 36 | 37 | build 38 | backend/src/main/resources/public/ 39 | backend/target 40 | backend/build 41 | backend/.mvn 42 | backend/mvnw 43 | frontend/dist/ 44 | frontend/build 45 | frontend/node/ 46 | frontend/node_modules/ 47 | frontend/npm-debug.log 48 | frontend/target 49 | .gradle 50 | 51 | !.mvn/wrapper/maven-wrapper.jar 52 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | script: mvn clean install jacoco:report coveralls:report 5 | cache: 6 | directories: 7 | - node_modules -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java -Dserver.port=$PORT -jar backend/build/libs/backend-0.0.1-SNAPSHOT.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kotlin-spring-vue-gradle 2 | Koltin + Spring + Vue.js + Gradle demo app 3 | 4 | Tutorial 5 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /backend/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 2 | 3 | plugins { 4 | id("org.springframework.boot") version "2.1.3.RELEASE" 5 | id("io.spring.dependency-management") version "1.0.8.RELEASE" 6 | kotlin("jvm") version "1.3.50" 7 | kotlin("plugin.spring") version "1.3.50" 8 | id("org.jetbrains.kotlin.plugin.jpa") version "1.3.50" 9 | } 10 | 11 | group = "com.kotlin-spring-vue" 12 | version = "0.0.1-SNAPSHOT" 13 | java.sourceCompatibility = JavaVersion.VERSION_1_8 14 | 15 | repositories { 16 | mavenCentral() 17 | maven { 18 | url = uri("https://plugins.gradle.org/m2/") 19 | } 20 | } 21 | 22 | dependencies { 23 | runtimeOnly(project(":frontend")) 24 | implementation("org.springframework.boot:spring-boot-starter-actuator:2.1.3.RELEASE") 25 | implementation("org.springframework.boot:spring-boot-starter-web:2.1.3.RELEASE") 26 | implementation("org.springframework.boot:spring-boot-starter-data-jpa:2.1.3.RELEASE") 27 | implementation("org.springframework.boot:spring-boot-starter-mail:2.1.3.RELEASE") 28 | implementation("org.springframework.boot:spring-boot-starter-security:2.1.3.RELEASE") 29 | implementation("org.postgresql:postgresql:42.2.5") 30 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf:2.1.3.RELEASE") 31 | implementation("commons-io:commons-io:2.4") 32 | implementation("io.jsonwebtoken:jjwt:0.9.0") 33 | implementation("io.jsonwebtoken:jjwt-api:0.10.6") 34 | implementation("com.mashape.unirest:unirest-java:1.4.9") 35 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8") 36 | runtimeOnly("org.springframework.boot:spring-boot-devtools:2.1.3.RELEASE") 37 | implementation("org.jetbrains.kotlin:kotlin-reflect") 38 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 39 | implementation("org.jetbrains.kotlin:kotlin-noarg:1.3.50") 40 | testImplementation("org.springframework.boot:spring-boot-starter-test") { 41 | exclude(group = "org.junit.vintage", module = "junit-vintage-engine") 42 | } 43 | } 44 | 45 | tasks.withType { 46 | kotlinOptions { 47 | freeCompilerArgs = listOf("-Xjsr305=strict") 48 | jvmTarget = "1.8" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /backend/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /backend/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/BackendApplication.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class BackendApplication 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/config/WebSecurityConfig.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.config 2 | 3 | import org.springframework.context.annotation.Bean 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 7 | import org.springframework.security.config.http.SessionCreationPolicy 8 | import org.springframework.security.authentication.AuthenticationManager 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity 11 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity 12 | import org.springframework.security.config.annotation.web.builders.HttpSecurity 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder 15 | 16 | import com.kotlinspringvue.backend.jwt.JwtAuthEntryPoint 17 | import com.kotlinspringvue.backend.jwt.JwtAuthTokenFilter 18 | import com.kotlinspringvue.backend.service.UserDetailsServiceImpl 19 | import org.springframework.web.cors.CorsConfiguration 20 | import org.springframework.web.cors.CorsConfigurationSource 21 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource 22 | import java.util.* 23 | import org.springframework.security.config.annotation.web.builders.WebSecurity 24 | 25 | 26 | @Configuration 27 | @EnableWebSecurity 28 | @EnableGlobalMethodSecurity(prePostEnabled = true) 29 | class WebSecurityConfig : WebSecurityConfigurerAdapter() { 30 | 31 | @Autowired 32 | lateinit var userDetailsService: UserDetailsServiceImpl 33 | 34 | @Autowired 35 | lateinit var unauthorizedHandler: JwtAuthEntryPoint 36 | 37 | @Bean 38 | fun bCryptPasswordEncoder(): BCryptPasswordEncoder { 39 | return BCryptPasswordEncoder() 40 | } 41 | 42 | @Bean 43 | fun authenticationJwtTokenFilter(): JwtAuthTokenFilter { 44 | return JwtAuthTokenFilter() 45 | } 46 | 47 | @Throws(Exception::class) 48 | override fun configure(authenticationManagerBuilder: AuthenticationManagerBuilder) { 49 | authenticationManagerBuilder 50 | .userDetailsService(userDetailsService) 51 | .passwordEncoder(bCryptPasswordEncoder()) 52 | } 53 | 54 | @Bean 55 | @Throws(Exception::class) 56 | override fun authenticationManagerBean(): AuthenticationManager { 57 | return super.authenticationManagerBean() 58 | } 59 | 60 | @Bean 61 | fun corsConfigurationSource(): CorsConfigurationSource { 62 | val configuration = CorsConfiguration() 63 | configuration.allowedOrigins = Arrays.asList("http://localhost:8080", "http://localhost:8081", "https://kotlin-spring-vue-gradle-demo.herokuapp.com") 64 | configuration.allowedHeaders = Arrays.asList("*") 65 | configuration.allowedMethods = Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS") 66 | configuration.allowCredentials = true 67 | configuration.maxAge = 3600 68 | val source = UrlBasedCorsConfigurationSource() 69 | source.registerCorsConfiguration("/**", configuration) 70 | return source 71 | } 72 | 73 | @Throws(Exception::class) 74 | override fun configure(http: HttpSecurity) { 75 | http 76 | .cors().and() 77 | .csrf().disable().authorizeRequests() 78 | .antMatchers("/**").permitAll() 79 | .anyRequest().authenticated() 80 | .and() 81 | .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() 82 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 83 | 84 | http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter::class.java) 85 | http.headers().cacheControl().disable() 86 | } 87 | 88 | @Throws(Exception::class) 89 | override fun configure(web: WebSecurity) { 90 | web.ignoring().antMatchers("/api/signin", "/api/signup", "/api/registrationConfirm") 91 | } 92 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/controller/AuthController.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.controller 2 | 3 | import com.kotlinspringvue.backend.email.EmailService 4 | import javax.validation.Valid 5 | import java.util.* 6 | import java.util.stream.Collectors 7 | 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.http.HttpStatus 10 | import org.springframework.http.ResponseEntity 11 | import org.springframework.security.authentication.AuthenticationManager 12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken 13 | import org.springframework.security.core.context.SecurityContextHolder 14 | import org.springframework.security.crypto.password.PasswordEncoder 15 | import org.springframework.security.core.GrantedAuthority 16 | import org.springframework.security.core.authority.SimpleGrantedAuthority 17 | import org.springframework.ui.Model 18 | 19 | import com.kotlinspringvue.backend.model.LoginUser 20 | import com.kotlinspringvue.backend.model.NewUser 21 | import com.kotlinspringvue.backend.web.response.SuccessfulSigninResponse 22 | import com.kotlinspringvue.backend.web.response.ResponseMessage 23 | import com.kotlinspringvue.backend.jpa.User 24 | import com.kotlinspringvue.backend.repository.UserRepository 25 | import com.kotlinspringvue.backend.repository.RoleRepository 26 | import com.kotlinspringvue.backend.jwt.JwtProvider 27 | import com.kotlinspringvue.backend.service.ReCaptchaService 28 | import com.kotlinspringvue.backend.service.UserDetailsService 29 | import org.springframework.beans.factory.annotation.Value 30 | import org.springframework.context.ApplicationEventPublisher 31 | import org.springframework.web.bind.annotation.* 32 | import org.springframework.web.context.request.WebRequest 33 | import java.io.UnsupportedEncodingException 34 | import javax.servlet.http.Cookie 35 | import javax.servlet.http.HttpServletRequest 36 | import javax.servlet.http.HttpServletResponse 37 | import com.kotlinspringvue.backend.service.UserDetailsServiceImpl.Companion.TOKEN_VALID 38 | import com.kotlinspringvue.backend.service.UserDetailsServiceImpl.Companion.TOKEN_INVALID 39 | import com.kotlinspringvue.backend.service.UserDetailsServiceImpl.Companion.TOKEN_EXPIRED 40 | 41 | @RestController 42 | @RequestMapping("/api/auth") 43 | class AuthController() { 44 | 45 | @Value("\${ksvg.app.authCookieName}") 46 | lateinit var authCookieName: String 47 | 48 | @Value("\${ksvg.app.isCookieSecure}") 49 | var isCookieSecure: Boolean = true 50 | 51 | @Autowired 52 | lateinit var authenticationManager: AuthenticationManager 53 | 54 | @Autowired 55 | lateinit var userRepository: UserRepository 56 | 57 | @Autowired 58 | lateinit var roleRepository: RoleRepository 59 | 60 | @Autowired 61 | lateinit var encoder: PasswordEncoder 62 | 63 | @Autowired 64 | lateinit var jwtProvider: JwtProvider 65 | 66 | @Autowired 67 | lateinit var captchaService: ReCaptchaService 68 | 69 | @Autowired 70 | lateinit var userService: UserDetailsService 71 | 72 | @Autowired 73 | lateinit var emailService: EmailService 74 | 75 | @PostMapping("/signin") 76 | fun authenticateUser(@Valid @RequestBody loginRequest: LoginUser, response: HttpServletResponse): ResponseEntity<*> { 77 | 78 | val userCandidate: Optional = userRepository.findByUsername(loginRequest.username!!) 79 | 80 | if (!captchaService.validateCaptcha(loginRequest.recaptchaToken!!)) { 81 | return ResponseEntity(ResponseMessage("Validation failed (ReCaptcha v2)"), 82 | HttpStatus.BAD_REQUEST) 83 | } 84 | else if (userCandidate.isPresent) { 85 | val user: User = userCandidate.get() 86 | 87 | if (!user.enabled) { 88 | return ResponseEntity(ResponseMessage("Account is not verified yet! Please, follow the link in the confirmation email."), 89 | HttpStatus.UNAUTHORIZED) 90 | } 91 | 92 | val authentication = authenticationManager.authenticate( 93 | UsernamePasswordAuthenticationToken(loginRequest.username, loginRequest.password)) 94 | SecurityContextHolder.getContext().setAuthentication(authentication) 95 | val jwt: String = jwtProvider.generateJwtToken(user.username!!) 96 | 97 | val cookie: Cookie = Cookie(authCookieName, jwt) 98 | cookie.maxAge = jwtProvider.jwtExpiration!! 99 | cookie.secure = isCookieSecure 100 | cookie.isHttpOnly = true 101 | cookie.path = "/" 102 | response.addCookie(cookie) 103 | 104 | val authorities: List = user.roles!!.stream().map({ role -> SimpleGrantedAuthority(role.name)}).collect(Collectors.toList()) 105 | return ResponseEntity.ok(SuccessfulSigninResponse(user.username, authorities)) 106 | } else { 107 | return ResponseEntity(ResponseMessage("User not found!"), 108 | HttpStatus.BAD_REQUEST) 109 | } 110 | } 111 | 112 | @PostMapping("/signup") 113 | fun registerUser(@Valid @RequestBody newUser: NewUser): ResponseEntity<*> { 114 | 115 | val userCandidate: Optional = userRepository.findByUsername(newUser.username!!) 116 | 117 | if (!captchaService.validateCaptcha(newUser.recaptchaToken!!)) { 118 | return ResponseEntity(ResponseMessage("Validation failed (ReCaptcha v2)"), 119 | HttpStatus.BAD_REQUEST) 120 | } else if (!userCandidate.isPresent) { 121 | if (usernameExists(newUser.username!!)) { 122 | return ResponseEntity(ResponseMessage("Username is already taken!"), 123 | HttpStatus.BAD_REQUEST) 124 | } else if (emailExists(newUser.email!!)) { 125 | return ResponseEntity(ResponseMessage("Email is already in use!"), 126 | HttpStatus.BAD_REQUEST) 127 | } 128 | 129 | try { 130 | // Creating user's account 131 | val user = User( 132 | 0, 133 | newUser.username!!, 134 | newUser.firstName!!, 135 | newUser.lastName!!, 136 | newUser.email!!, 137 | encoder.encode(newUser.password), 138 | false 139 | ) 140 | user.roles = Arrays.asList(roleRepository.findByName("ROLE_USER")) 141 | val registeredUser = userRepository.save(user) 142 | 143 | emailService.sendRegistrationConfirmationEmail(registeredUser) 144 | } catch (e: Exception) { 145 | return ResponseEntity(ResponseMessage("Server error. Please, contact site owner"), 146 | HttpStatus.SERVICE_UNAVAILABLE) 147 | } 148 | 149 | return ResponseEntity(ResponseMessage("Please, follow the link in the confirmation email to complete the registration."), HttpStatus.OK) 150 | } else { 151 | return ResponseEntity(ResponseMessage("User already exists!"), 152 | HttpStatus.BAD_REQUEST) 153 | } 154 | } 155 | 156 | @PostMapping("/registrationConfirm") 157 | @CrossOrigin(origins = ["*"]) 158 | @Throws(UnsupportedEncodingException::class) 159 | fun confirmRegistration(request: HttpServletRequest, model: Model, @RequestParam("token") token: String): ResponseEntity<*> { 160 | 161 | when(userService.validateVerificationToken(token)) { 162 | TOKEN_VALID -> return ResponseEntity.ok(ResponseMessage("Registration confirmed")) 163 | TOKEN_INVALID -> return ResponseEntity(ResponseMessage("Token is invalid!"), HttpStatus.BAD_REQUEST) 164 | TOKEN_EXPIRED -> return ResponseEntity(ResponseMessage("Token is invalid!"), HttpStatus.UNAUTHORIZED) 165 | } 166 | 167 | return ResponseEntity(ResponseMessage("Server error. Please, contact site owner"), HttpStatus.SERVICE_UNAVAILABLE) 168 | } 169 | 170 | @PostMapping("/logout") 171 | fun logout(response: HttpServletResponse): ResponseEntity<*> { 172 | val cookie: Cookie = Cookie(authCookieName, null) 173 | cookie.maxAge = 0 174 | cookie.secure = isCookieSecure 175 | cookie.isHttpOnly = true 176 | cookie.path = "/" 177 | response.addCookie(cookie) 178 | 179 | return ResponseEntity.ok(ResponseMessage("Successfully logged")) 180 | } 181 | 182 | private fun emailExists(email: String): Boolean { 183 | return userRepository.findByUsername(email).isPresent 184 | } 185 | 186 | private fun usernameExists(username: String): Boolean { 187 | return userRepository.findByUsername(username).isPresent 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/controller/BackendController.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.controller 2 | 3 | import org.springframework.web.bind.annotation.* 4 | import com.kotlinspringvue.backend.model.Greeting 5 | import java.util.concurrent.atomic.AtomicLong 6 | import com.kotlinspringvue.backend.repository.PersonRepository 7 | import org.springframework.beans.factory.annotation.Autowired 8 | import org.springframework.security.access.prepost.PreAuthorize 9 | import org.springframework.security.core.Authentication 10 | import com.kotlinspringvue.backend.repository.UserRepository 11 | import com.kotlinspringvue.backend.jpa.User 12 | import com.kotlinspringvue.backend.email.EmailService 13 | import com.kotlinspringvue.backend.web.response.ResponseMessage 14 | import org.springframework.beans.factory.annotation.Value 15 | import org.springframework.http.ResponseEntity 16 | import org.springframework.http.HttpStatus 17 | 18 | @RestController 19 | @RequestMapping("/api") 20 | class BackendController() { 21 | 22 | @Value("\${spring.mail.username}") 23 | lateinit var addressee: String 24 | 25 | @Autowired 26 | lateinit var personRepository: PersonRepository 27 | 28 | @Autowired 29 | lateinit var userRepository: UserRepository 30 | 31 | @Autowired 32 | lateinit var emailService: EmailService 33 | 34 | val counter = AtomicLong() 35 | 36 | @GetMapping("/greeting") 37 | fun greeting(@RequestParam(value = "name", defaultValue = "World") name: String) = 38 | Greeting(counter.incrementAndGet(), "Hello, $name") 39 | 40 | @GetMapping("/persons") 41 | fun getPersons() = personRepository.findAll() 42 | 43 | @GetMapping("/usercontent") 44 | @PreAuthorize("hasRole('USER') or hasRole('ADMIN')") 45 | @ResponseBody 46 | fun getUserContent(authentication: Authentication): String { 47 | val user: User = userRepository.findByUsername(authentication.name).get() 48 | return "Hello " + user.firstName + " " + user.lastName + "!" 49 | } 50 | 51 | @GetMapping("/admincontent") 52 | @PreAuthorize("hasRole('ADMIN')") 53 | @ResponseBody 54 | fun getAdminContent(): String { 55 | return "Admin's content" 56 | } 57 | 58 | @GetMapping("/sendSimpleEmail") 59 | @PreAuthorize("hasRole('USER')") 60 | fun sendSimpleEmail(): ResponseEntity<*> { 61 | try { 62 | //Uncomment to use 63 | //emailService.sendSimpleMessage(addressee, "Simple Email", "Hello! This is simple email") 64 | } catch (e: Exception) { 65 | return ResponseEntity(ResponseMessage("Error while sending message"), HttpStatus.BAD_REQUEST) 66 | } 67 | 68 | return ResponseEntity(ResponseMessage("Email has been sent"), HttpStatus.OK) 69 | } 70 | 71 | @GetMapping("/sendTemplateEmail") 72 | @PreAuthorize("hasRole('USER')") 73 | fun sendTemplateEmail(): ResponseEntity<*> { 74 | try { 75 | var params:MutableMap = mutableMapOf() 76 | params["addresseeName"] = addressee 77 | params["signatureImage"] = "https://coderlook.com/wp-content/uploads/2019/07/spring-by-pivotal.png" 78 | //Uncomment to use 79 | //emailService.sendSimpleMessageUsingTemplate(addressee, "Template Email", "emailTemplate", params) 80 | } catch (e: Exception) { 81 | return ResponseEntity(ResponseMessage("Error while sending message"), HttpStatus.BAD_REQUEST) 82 | } 83 | 84 | return ResponseEntity(ResponseMessage("Email has been sent"), HttpStatus.OK) 85 | } 86 | 87 | @GetMapping("/sendHtmlEmail") 88 | @PreAuthorize("hasRole('USER')") 89 | fun sendHtmlEmail(): ResponseEntity<*> { 90 | try { 91 | //Uncomment to use 92 | //emailService.sendHtmlMessage(addressee, "HTML Email", "

Hello!

This is HTML email

") 93 | } catch (e: Exception) { 94 | return ResponseEntity(ResponseMessage("Error while sending message"), HttpStatus.BAD_REQUEST) 95 | } 96 | 97 | return ResponseEntity(ResponseMessage("Email has been sent"), HttpStatus.OK) 98 | } 99 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/email/EmailService.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.email 2 | 3 | import com.kotlinspringvue.backend.jpa.User 4 | 5 | interface EmailService { 6 | fun sendSimpleMessage(to: String, 7 | subject: String, 8 | text: String) 9 | 10 | fun sendSimpleMessageUsingTemplate(to: String, 11 | subject: String, 12 | template: String, 13 | params:MutableMap) 14 | 15 | fun sendMessageWithAttachment(to: String, 16 | subject: String, 17 | text: String, 18 | pathToAttachment: String) 19 | 20 | fun sendHtmlMessage(to: String, 21 | subject: String, 22 | htmlMsg: String) 23 | 24 | fun sendRegistrationConfirmationEmail(user: User) 25 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/email/EmailServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.email 2 | 3 | import com.kotlinspringvue.backend.jpa.User 4 | import com.kotlinspringvue.backend.service.UserDetailsServiceImpl 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.beans.factory.annotation.Value 7 | import org.springframework.core.io.FileSystemResource 8 | import org.springframework.mail.MailException 9 | import org.springframework.mail.SimpleMailMessage 10 | import org.springframework.mail.javamail.JavaMailSender 11 | import org.springframework.mail.javamail.MimeMessageHelper 12 | import org.springframework.stereotype.Component 13 | import org.thymeleaf.spring5.SpringTemplateEngine 14 | import org.thymeleaf.context.Context 15 | import java.io.File 16 | import javax.mail.MessagingException 17 | import org.springframework.core.env.Environment 18 | import java.util.* 19 | 20 | @Component 21 | class EmailServiceImpl : EmailService { 22 | 23 | @Value("\${spring.mail.username}") 24 | lateinit var sender: String 25 | 26 | @Value("\${host.url}") 27 | lateinit var hostUrl: String 28 | 29 | @Autowired 30 | lateinit var userDetailsService: UserDetailsServiceImpl 31 | 32 | @Autowired 33 | lateinit var environment: Environment 34 | 35 | @Autowired 36 | var emailSender: JavaMailSender? = null 37 | 38 | @Autowired 39 | lateinit var templateEngine: SpringTemplateEngine 40 | 41 | override fun sendSimpleMessage(to: String, subject: String, text: String) { 42 | try { 43 | val message = SimpleMailMessage() 44 | message.setTo(to) 45 | message.setFrom(sender) 46 | message.setSubject(subject) 47 | message.setText(text) 48 | 49 | emailSender!!.send(message) 50 | } catch (exception: MailException) { 51 | exception.printStackTrace() 52 | } 53 | 54 | } 55 | 56 | override fun sendSimpleMessageUsingTemplate(to: String, 57 | subject: String, 58 | template: String, 59 | params:MutableMap) { 60 | val message = emailSender!!.createMimeMessage() 61 | val helper = MimeMessageHelper(message, true, "utf-8") 62 | var context: Context = Context() 63 | context.setVariables(params) 64 | val html: String = templateEngine.process(template, context) 65 | 66 | helper.setTo(to) 67 | helper.setFrom(sender) 68 | helper.setText(html, true) 69 | helper.setSubject(subject) 70 | 71 | emailSender!!.send(message) 72 | } 73 | 74 | override fun sendMessageWithAttachment(to: String, 75 | subject: String, 76 | text: String, 77 | pathToAttachment: String) { 78 | try { 79 | val message = emailSender!!.createMimeMessage() 80 | val helper = MimeMessageHelper(message, true) 81 | 82 | helper.setTo(to) 83 | helper.setFrom(sender) 84 | helper.setSubject(subject) 85 | helper.setText(text) 86 | 87 | val file = FileSystemResource(File(pathToAttachment)) 88 | helper.addAttachment("Invoice", file) 89 | 90 | emailSender!!.send(message) 91 | } catch (e: MessagingException) { 92 | e.printStackTrace() 93 | } 94 | 95 | } 96 | 97 | override fun sendHtmlMessage(to: String, subject: String, htmlMsg: String) { 98 | try { 99 | val message = emailSender!!.createMimeMessage() 100 | message.setContent(htmlMsg, "text/html") 101 | 102 | val helper = MimeMessageHelper(message, false, "utf-8") 103 | 104 | helper.setTo(to) 105 | helper.setFrom(sender) 106 | helper.setSubject(subject) 107 | 108 | emailSender!!.send(message) 109 | } catch (exception: MailException) { 110 | exception.printStackTrace() 111 | } 112 | } 113 | 114 | override fun sendRegistrationConfirmationEmail(user: User) { 115 | val token = UUID.randomUUID().toString() 116 | userDetailsService.createVerificationTokenForUser(token, user) 117 | val link = "$hostUrl/?token=$token&confirmRegistration=true" 118 | val msg = "

Please, follow the link to complete your registration:

$link

" 119 | user.email?.let{sendHtmlMessage(user.email!!, "KSVG APP: Registration Confirmation", msg)} 120 | } 121 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jpa/Person.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jpa 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 | @Entity 11 | @Table (name="person") 12 | data class Person( 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.AUTO) 16 | val id: Long, 17 | 18 | @Column(nullable = false) 19 | val name: String 20 | ) 21 | -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jpa/Role.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jpa 2 | 3 | import javax.persistence.* 4 | 5 | @Entity 6 | @Table(name = "roles") 7 | data class Role ( 8 | 9 | @Id 10 | @GeneratedValue(strategy = GenerationType.AUTO) 11 | val id: Long, 12 | 13 | @Column(name="name") 14 | val name: String 15 | 16 | ) -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jpa/User.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jpa 2 | 3 | import javax.persistence.* 4 | 5 | @Entity 6 | @Table(name = "users") 7 | data class User ( 8 | 9 | @Id 10 | @GeneratedValue(strategy = GenerationType.AUTO) 11 | val id: Long? = 0, 12 | 13 | @Column(name="username") 14 | var username: String?=null, 15 | 16 | @Column(name="first_name") 17 | var firstName: String?=null, 18 | 19 | @Column(name="last_name") 20 | var lastName: String?=null, 21 | 22 | @Column(name="email") 23 | var email: String?=null, 24 | 25 | @Column(name="password") 26 | var password: String?=null, 27 | 28 | @Column(name="enabled") 29 | var enabled: Boolean = false, 30 | 31 | @ManyToMany(fetch = FetchType.EAGER) 32 | @JoinTable( 33 | name = "users_roles", 34 | joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")], 35 | inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")] 36 | ) 37 | var roles: Collection? = null 38 | ) -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jpa/VerificationToken.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jpa 2 | 3 | import java.sql.* 4 | import javax.persistence.* 5 | import java.util.Calendar 6 | 7 | @Entity 8 | @Table(name = "verification_token") 9 | data class VerificationToken( 10 | 11 | @Id 12 | @GeneratedValue(strategy = GenerationType.AUTO) 13 | val id: Long? = 0, 14 | 15 | @Column(name = "token") 16 | var token: String? = null, 17 | 18 | @Column(name = "expiry_date") 19 | val expiryDate: Date, 20 | 21 | @OneToOne(targetEntity = User::class, fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST]) 22 | @JoinColumn(nullable = false, name = "user_id") 23 | val user: User 24 | ) { 25 | constructor(token: String?, user: User) : this(0, token, calculateExpiryDate(1440), user) 26 | } 27 | 28 | private fun calculateExpiryDate(expiryTimeInMinutes: Int): Date { 29 | val cal = Calendar.getInstance() 30 | cal.time = Timestamp(cal.time.time) 31 | cal.add(Calendar.MINUTE, expiryTimeInMinutes) 32 | return Date(cal.time.time) 33 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jwt/JwtAuthEntryPoint.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jwt 2 | 3 | import java.io.IOException 4 | 5 | import javax.servlet.ServletException 6 | import javax.servlet.http.HttpServletRequest 7 | import javax.servlet.http.HttpServletResponse 8 | 9 | import org.slf4j.Logger 10 | import org.slf4j.LoggerFactory 11 | import org.springframework.security.core.AuthenticationException 12 | import org.springframework.security.web.AuthenticationEntryPoint 13 | import org.springframework.stereotype.Component 14 | 15 | @Component 16 | class JwtAuthEntryPoint: AuthenticationEntryPoint { 17 | 18 | @Throws(IOException::class, ServletException::class) 19 | override fun commence(request: HttpServletRequest, 20 | response: HttpServletResponse, 21 | e: AuthenticationException) { 22 | 23 | logger.error("Unauthorized error. Message - {}", e.message) 24 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid credentials") 25 | } 26 | 27 | companion object { 28 | private val logger = LoggerFactory.getLogger(JwtAuthEntryPoint::class.java) 29 | } 30 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jwt/JwtAuthTokenFilter.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jwt 2 | 3 | import java.io.IOException 4 | 5 | import javax.servlet.FilterChain 6 | import javax.servlet.ServletException 7 | import javax.servlet.http.HttpServletRequest 8 | import javax.servlet.http.HttpServletResponse 9 | 10 | import org.slf4j.LoggerFactory 11 | import org.springframework.beans.factory.annotation.Autowired 12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken 13 | import org.springframework.security.core.context.SecurityContextHolder 14 | import org.springframework.security.core.userdetails.UserDetails 15 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource 16 | import org.springframework.web.filter.OncePerRequestFilter 17 | 18 | import com.kotlinspringvue.backend.service.UserDetailsServiceImpl 19 | import org.springframework.beans.factory.annotation.Value 20 | 21 | class JwtAuthTokenFilter : OncePerRequestFilter() { 22 | 23 | @Value("\${ksvg.app.authCookieName}") 24 | lateinit var authCookieName: String 25 | 26 | @Autowired 27 | private val tokenProvider: JwtProvider? = null 28 | 29 | @Autowired 30 | private val userDetailsService: UserDetailsServiceImpl? = null 31 | 32 | @Throws(ServletException::class, IOException::class) 33 | override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) { 34 | try { 35 | 36 | val jwt = getJwt(request) 37 | if (jwt != null && tokenProvider!!.validateJwtToken(jwt)) { 38 | val username = tokenProvider.getUserNameFromJwtToken(jwt) 39 | 40 | val userDetails = userDetailsService!!.loadUserByUsername(username) 41 | val authentication = UsernamePasswordAuthenticationToken( 42 | userDetails, null, userDetails.getAuthorities()) 43 | authentication.setDetails(WebAuthenticationDetailsSource().buildDetails(request)) 44 | 45 | SecurityContextHolder.getContext().setAuthentication(authentication) 46 | } 47 | } catch (e: Exception) { 48 | logger.error("Can NOT set user authentication -> Message: {}", e) 49 | } 50 | 51 | filterChain.doFilter(request, response) 52 | } 53 | 54 | private fun getJwt(request: HttpServletRequest): String? { 55 | try { 56 | for (cookie in request.cookies) { 57 | if (cookie.name == authCookieName) { 58 | return cookie.value 59 | } 60 | } 61 | } catch (e: Exception) { 62 | println("No token found!") 63 | } 64 | return null 65 | } 66 | 67 | companion object { 68 | private val logger = LoggerFactory.getLogger(JwtAuthTokenFilter::class.java) 69 | } 70 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/jwt/JwtProvider.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.jwt 2 | 3 | import io.jsonwebtoken.* 4 | import org.springframework.beans.factory.annotation.Autowired 5 | import org.slf4j.Logger 6 | import org.slf4j.LoggerFactory 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.security.core.Authentication 9 | import org.springframework.stereotype.Component 10 | import org.springframework.security.core.GrantedAuthority 11 | import org.springframework.security.core.authority.SimpleGrantedAuthority 12 | import com.kotlinspringvue.backend.repository.UserRepository 13 | import java.util.Date 14 | 15 | 16 | @Component 17 | public class JwtProvider { 18 | 19 | private val logger: Logger = LoggerFactory.getLogger(JwtProvider::class.java) 20 | 21 | @Autowired 22 | lateinit var userRepository: UserRepository 23 | 24 | @Value("\${ksvg.app.jwtSecret}") 25 | lateinit var jwtSecret: String 26 | 27 | @Value("\${ksvg.app.jwtExpiration}") 28 | var jwtExpiration:Int?=0 29 | 30 | fun generateJwtToken(username: String): String { 31 | return Jwts.builder() 32 | .setSubject(username) 33 | .setIssuedAt(Date()) 34 | .setExpiration(Date((Date()).getTime() + jwtExpiration!! * 1000)) 35 | .signWith(SignatureAlgorithm.HS512, jwtSecret) 36 | .compact() 37 | } 38 | 39 | fun validateJwtToken(authToken: String): Boolean { 40 | try { 41 | Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken) 42 | return true 43 | } catch (e: SignatureException) { 44 | logger.error("Invalid JWT signature -> Message: {} ", e) 45 | } catch (e: MalformedJwtException) { 46 | logger.error("Invalid JWT token -> Message: {}", e) 47 | } catch (e: ExpiredJwtException) { 48 | logger.error("Expired JWT token -> Message: {}", e) 49 | } catch (e: UnsupportedJwtException) { 50 | logger.error("Unsupported JWT token -> Message: {}", e) 51 | } catch (e: IllegalArgumentException) { 52 | logger.error("JWT claims string is empty -> Message: {}", e) 53 | } 54 | 55 | return false 56 | } 57 | 58 | fun getUserNameFromJwtToken(token: String): String { 59 | return Jwts.parser() 60 | .setSigningKey(jwtSecret) 61 | .parseClaimsJws(token) 62 | .getBody().getSubject() 63 | } 64 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/model/Greeting.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.model 2 | 3 | data class Greeting(val id: Long, val content: String) -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/model/LoginUser.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.model 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import java.io.Serializable 5 | 6 | class LoginUser : Serializable { 7 | 8 | @JsonProperty("username") 9 | var username: String? = null 10 | 11 | @JsonProperty("password") 12 | var password: String? = null 13 | 14 | @JsonProperty("recapctha_token") 15 | var recaptchaToken: String? = null 16 | 17 | constructor() {} 18 | 19 | constructor(username: String, password: String, recaptchaToken: String) { 20 | this.username = username 21 | this.password = password 22 | this.recaptchaToken = recaptchaToken 23 | } 24 | 25 | companion object { 26 | 27 | private const val serialVersionUID = -1764970284520387975L 28 | } 29 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/model/NewUser.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.model 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import java.io.Serializable 5 | 6 | class NewUser : Serializable { 7 | 8 | @JsonProperty("username") 9 | var username: String? = null 10 | 11 | @JsonProperty("firstName") 12 | var firstName: String? = null 13 | 14 | @JsonProperty("lastName") 15 | var lastName: String? = null 16 | 17 | @JsonProperty("email") 18 | var email: String? = null 19 | 20 | @JsonProperty("password") 21 | var password: String? = null 22 | 23 | @JsonProperty("recapctha_token") 24 | var recaptchaToken: String? = null 25 | 26 | constructor() {} 27 | 28 | constructor(username: String, firstName: String, lastName: String, email: String, password: String, recaptchaToken: String) { 29 | this.username = username 30 | this.firstName = firstName 31 | this.lastName = lastName 32 | this.email = email 33 | this.password = password 34 | this.recaptchaToken = recaptchaToken 35 | } 36 | 37 | companion object { 38 | 39 | private const val serialVersionUID = -1764970284520387975L 40 | } 41 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/repository/PersonRepository.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.repository 2 | 3 | import com.kotlinspringvue.backend.jpa.Person 4 | import org.springframework.stereotype.Repository 5 | import org.springframework.data.repository.CrudRepository 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import org.springframework.data.repository.query.Param 8 | 9 | @Repository 10 | interface PersonRepository: CrudRepository { 11 | 12 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/repository/RoleRepository.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.repository 2 | 3 | import com.kotlinspringvue.backend.jpa.Role 4 | import org.springframework.data.repository.CrudRepository 5 | import org.springframework.data.repository.query.Param 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | 8 | interface RoleRepository : JpaRepository { 9 | 10 | fun findByName(@Param("name") name: String): Role 11 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/repository/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.repository 2 | 3 | import java.util.Optional 4 | import com.kotlinspringvue.backend.jpa.User 5 | import org.springframework.data.repository.CrudRepository 6 | import org.springframework.data.repository.query.Param 7 | import org.springframework.data.jpa.repository.JpaRepository 8 | import javax.transaction.Transactional 9 | 10 | interface UserRepository: JpaRepository { 11 | 12 | fun existsByUsername(@Param("username") username: String): Boolean 13 | 14 | fun findByUsername(@Param("username") username: String): Optional 15 | 16 | fun findByEmail(@Param("email") email: String): Optional 17 | 18 | @Transactional 19 | fun deleteByUsername(@Param("username") username: String) 20 | 21 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/repository/VerificationTokenRepository.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.repository 2 | 3 | import com.kotlinspringvue.backend.jpa.VerificationToken 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | import java.util.* 6 | 7 | interface VerificationTokenRepository : JpaRepository { 8 | fun findByToken(token: String): Optional 9 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/service/ReCaptchaService.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.service 2 | 3 | import org.springframework.beans.factory.annotation.Value 4 | import org.springframework.stereotype.Service 5 | import org.springframework.web.client.RestOperations 6 | import org.springframework.beans.factory.annotation.Autowired 7 | import com.mashape.unirest.http.HttpResponse 8 | import com.mashape.unirest.http.JsonNode 9 | import com.mashape.unirest.http.Unirest 10 | 11 | 12 | @Service("captchaService") 13 | class ReCaptchaService { 14 | 15 | val BASE_VERIFY_URL: String = "https://www.google.com/recaptcha/api/siteverify" 16 | 17 | @Autowired 18 | private val restTemplate: RestOperations? = null 19 | 20 | @Value("\${google.recaptcha.key.site}") 21 | lateinit var keySite: String 22 | 23 | @Value("\${google.recaptcha.key.secret}") 24 | lateinit var keySecret: String 25 | 26 | fun validateCaptcha(token: String): Boolean { 27 | 28 | val url: String = String.format(BASE_VERIFY_URL + "?secret=%s&response=%s", keySecret, token) 29 | 30 | val jsonResponse: HttpResponse = Unirest.get(url) 31 | .header("accept", "application/json").queryString("apiKey", "123") 32 | .asJson() 33 | 34 | return (jsonResponse.getStatus() == 200) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/service/UserDetailsService.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.service 2 | 3 | import com.kotlinspringvue.backend.jpa.User 4 | import org.springframework.security.core.userdetails.UserDetailsService 5 | 6 | interface UserDetailsService : UserDetailsService { 7 | 8 | fun createVerificationTokenForUser(token: String, user: User) 9 | 10 | fun validateVerificationToken(token: String): String 11 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/service/UserDetailsServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.service 2 | 3 | import com.kotlinspringvue.backend.jpa.User 4 | import com.kotlinspringvue.backend.jpa.VerificationToken 5 | import com.kotlinspringvue.backend.repository.UserRepository 6 | import com.kotlinspringvue.backend.repository.VerificationTokenRepository 7 | 8 | import org.springframework.beans.factory.annotation.Autowired 9 | import org.springframework.security.core.userdetails.UserDetails 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException 11 | import org.springframework.stereotype.Service 12 | import org.springframework.security.core.GrantedAuthority 13 | import org.springframework.security.core.authority.SimpleGrantedAuthority 14 | import java.util.* 15 | import java.util.stream.Collectors 16 | 17 | @Service 18 | class UserDetailsServiceImpl : UserDetailsService { 19 | 20 | @Autowired 21 | lateinit var userRepository: UserRepository 22 | 23 | @Autowired 24 | lateinit var tokenRepository: VerificationTokenRepository 25 | 26 | @Throws(UsernameNotFoundException::class) 27 | override fun loadUserByUsername(username: String): UserDetails { 28 | val user = userRepository.findByUsername(username).get() 29 | 30 | val authorities: List = user.roles!!.stream().map({ role -> SimpleGrantedAuthority(role.name)}).collect(Collectors.toList()) 31 | 32 | return org.springframework.security.core.userdetails.User 33 | .withUsername(username) 34 | .password(user.password) 35 | .authorities(authorities) 36 | .accountExpired(false) 37 | .accountLocked(false) 38 | .credentialsExpired(false) 39 | .disabled(false) 40 | .build() 41 | } 42 | 43 | override fun createVerificationTokenForUser(token: String, user: User) { 44 | tokenRepository.save(VerificationToken(token, user)) 45 | } 46 | 47 | override fun validateVerificationToken(token: String): String { 48 | val verificationToken: Optional = tokenRepository.findByToken(token) 49 | 50 | if (verificationToken.isPresent) { 51 | val user: User = verificationToken.get().user 52 | val cal: Calendar = Calendar.getInstance() 53 | if ((verificationToken.get().expiryDate.time - cal.time.time) <= 0) { 54 | tokenRepository.delete(verificationToken.get()) 55 | return TOKEN_EXPIRED 56 | } 57 | 58 | user.enabled = true 59 | tokenRepository.delete(verificationToken.get()) 60 | userRepository.save(user) 61 | return TOKEN_VALID 62 | } else { 63 | return TOKEN_INVALID 64 | } 65 | 66 | } 67 | 68 | companion object { 69 | val TOKEN_VALID: String = "valid" 70 | val TOKEN_INVALID: String = "invalid" 71 | val TOKEN_EXPIRED: String = "expired" 72 | } 73 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/web/error/UserAlreadyExistException.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.web.error 2 | 3 | class UserAlreadyExistException : RuntimeException { 4 | 5 | constructor() : super() {} 6 | 7 | constructor(message: String, cause: Throwable) : super(message, cause) {} 8 | 9 | constructor(message: String) : super(message) {} 10 | 11 | constructor(cause: Throwable) : super(cause) {} 12 | 13 | companion object { 14 | 15 | private val serialVersionUID = 5861310537366287163L 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/web/response/JwtResponse.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.web.response 2 | 3 | import org.springframework.security.core.GrantedAuthority 4 | 5 | class JwtResponse(var accessToken: String?, var username: String?, val authorities: Collection) { 6 | var type = "Bearer" 7 | } -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/web/response/ResponseMessage.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.web.response 2 | 3 | class ResponseMessage(var message: String?) -------------------------------------------------------------------------------- /backend/src/main/kotlin/com/kotlinspringvue/backend/web/response/SuccessfulSigninResponse.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend.web.response 2 | 3 | import org.springframework.security.core.GrantedAuthority 4 | 5 | class SuccessfulSigninResponse(var username: String?, val authorities: Collection) { 6 | 7 | } -------------------------------------------------------------------------------- /backend/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | host.url=${HOST_URL} 2 | 3 | spring.datasource.url=${SPRING_DATASOURCE_URL} 4 | spring.datasource.username=${SPRING_DATASOURCE_USERNAME} 5 | spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} 6 | 7 | spring.jpa.generate-ddl=true 8 | spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false 9 | spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL9Dialect 10 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 11 | 12 | # JWT Properties 13 | ksvg.app.jwtSecret=${JWT_KSVG_SECRET_KEY} 14 | ksvg.app.jwtExpiration=86400 15 | ksvg.app.authCookieName=${AUTH_COOKIE_NAME} 16 | ksvg.app.isCookieSecure=${IS_COOKIE_SECURE} 17 | 18 | # reCAPTCHA 19 | google.recaptcha.key.site=${GOOGLE_RECAPTCHA_KEY_SITE} 20 | google.recaptcha.key.secret=${GOOGLE_RECAPTCHA_KEY_SECRET} 21 | 22 | # SMTP 23 | spring.mail.host=${SMTP_MAIL_HOST} 24 | spring.mail.port=${SMTP_MAIL_PORT} 25 | spring.mail.username=${SMTP_MAIL_USERNAME} 26 | spring.mail.password=${SMTP_MAIL_PASSWORD} 27 | 28 | spring.mail.properties.mail.smtp.auth=true 29 | spring.mail.properties.mail.smtp.starttls.enable=true 30 | spring.mail.properties.mail.smtp.ssl.enable = true 31 | spring.mail.properties.mail.smtp.connectiontimeout=5000 32 | spring.mail.properties.mail.smtp.timeout=5000 33 | spring.mail.properties.mail.smtp.writetimeout=5000 -------------------------------------------------------------------------------- /backend/src/main/resources/templates/emailTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello 6 | 7 | 8 |

Hello!

9 |
10 |

Hello, dear:

11 |
12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /backend/src/test/kotlin/com/kotlinspringvue/backend/BackendApplicationTests.kt: -------------------------------------------------------------------------------- 1 | package com.kotlinspringvue.backend 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 | class BackendApplicationTests { 11 | 12 | @Test 13 | fun contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This is a general purpose Gradle build. 5 | * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds 6 | */ 7 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.siouan.frontend") version "1.2.1" 3 | id("java") 4 | } 5 | 6 | group = "com.kotlin-spring-vue" 7 | version = "0.0.1-SNAPSHOT" 8 | 9 | java { 10 | targetCompatibility = JavaVersion.VERSION_1_8 11 | } 12 | 13 | buildscript { 14 | repositories { 15 | mavenCentral() 16 | maven { 17 | url = uri("https://plugins.gradle.org/m2/") 18 | } 19 | } 20 | } 21 | 22 | frontend { 23 | nodeVersion.set("10.16.0") 24 | cleanScript.set("run clean") 25 | installScript.set("install") 26 | assembleScript.set("run build") 27 | } 28 | 29 | tasks.named("jar", Jar::class) { 30 | dependsOn("assembleFrontend") 31 | from("$buildDir/dist") 32 | into("static") 33 | } 34 | -------------------------------------------------------------------------------- /frontend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /frontend/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /frontend/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.0", 12 | "bootstrap": "^4.3.1", 13 | "bootstrap-vue": "^2.0.0-rc.26", 14 | "core-js": "^2.6.5", 15 | "jquery": "^3.4.1", 16 | "vue": "^2.6.10", 17 | "vue-recaptcha": "^1.2.0", 18 | "vue-router": "^3.0.7", 19 | "vuex": "^3.1.1" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^3.9.0", 23 | "@vue/cli-plugin-eslint": "^3.9.0", 24 | "@vue/cli-service": "^4.1.1", 25 | "babel-eslint": "^10.0.1", 26 | "eslint": "^5.16.0", 27 | "eslint-plugin-vue": "^5.0.0", 28 | "vue-template-compiler": "^2.6.10" 29 | }, 30 | "eslintConfig": { 31 | "root": true, 32 | "env": { 33 | "node": true 34 | }, 35 | "extends": [ 36 | "plugin:vue/essential", 37 | "eslint:recommended" 38 | ], 39 | "rules": { 40 | "no-console": "off" 41 | }, 42 | "parserOptions": { 43 | "parser": "babel-eslint" 44 | } 45 | }, 46 | "postcss": { 47 | "plugins": { 48 | "autoprefixer": {} 49 | } 50 | }, 51 | "browserslist": [ 52 | "> 1%", 53 | "last 2 versions" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadim-emelianov/kotlin-spring-vue-gradle/b676513c21cb0f06b0d0e81d35a148a67ddac006/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Kotlin + Spring Boot + Vue.js 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /frontend/src/assets/img/kotlin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadim-emelianov/kotlin-spring-vue-gradle/b676513c21cb0f06b0d0e81d35a148a67ddac006/frontend/src/assets/img/kotlin-logo.png -------------------------------------------------------------------------------- /frontend/src/assets/img/spring-boot-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadim-emelianov/kotlin-spring-vue-gradle/b676513c21cb0f06b0d0e81d35a148a67ddac006/frontend/src/assets/img/spring-boot-logo.png -------------------------------------------------------------------------------- /frontend/src/assets/img/vuejs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadim-emelianov/kotlin-spring-vue-gradle/b676513c21cb0f06b0d0e81d35a148a67ddac006/frontend/src/assets/img/vuejs-logo.png -------------------------------------------------------------------------------- /frontend/src/components/AdminPage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/components/EmailPage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 54 | 55 | -------------------------------------------------------------------------------- /frontend/src/components/Greeting.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 41 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /frontend/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/src/components/RegistrationConfirmPage.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 58 | 59 | -------------------------------------------------------------------------------- /frontend/src/components/SignIn.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 95 | 96 | -------------------------------------------------------------------------------- /frontend/src/components/SignUp.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 213 | 214 | -------------------------------------------------------------------------------- /frontend/src/components/UserPage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/components/http-common.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export const AXIOS = axios.create({ 4 | baseURL: `/api`, 5 | /*baseURL: `http://localhost:8080/api`,*/ 6 | headers: { 7 | 'Access-Control-Allow-Origin': ['http://localhost:8080', 'http://localhost:8081', 'https://kotlin-spring-vue-gradle-demo.herokuapp.com'], 8 | 'Access-Control-Allow-Methods': 'GET,POST,DELETE,PUT,OPTIONS', 9 | 'Access-Control-Allow-Headers': '*', 10 | 'Access-Control-Allow-Credentials': true 11 | } 12 | }) -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import { store } from './store' 5 | import BootstrapVue from 'bootstrap-vue' 6 | import 'bootstrap/dist/css/bootstrap.css' 7 | import 'bootstrap-vue/dist/bootstrap-vue.css' 8 | 9 | Vue.config.productionTip = false 10 | Vue.use(BootstrapVue) 11 | 12 | new Vue({ 13 | router, 14 | store, 15 | render: h => h(App) 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /frontend/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | /*import HelloWorld from '@/components/HelloWorld' 4 | import Greeting from '@/components/Greeting'*/ 5 | import Home from '@/components/Home' 6 | import SignIn from '@/components/SignIn' 7 | import SignUp from '@/components/SignUp' 8 | import AdminPage from '@/components/AdminPage' 9 | import UserPage from '@/components/UserPage' 10 | import EmailPage from '@/components/EmailPage' 11 | import RegistrationConfirmPage from '@/components/RegistrationConfirmPage' 12 | 13 | Vue.use(Router) 14 | 15 | export default new Router({ 16 | mode: 'history', 17 | routes: [ 18 | { 19 | path: '/', 20 | name: 'Home', 21 | component: Home 22 | }, 23 | { 24 | path: '/home', 25 | name: 'Home', 26 | component: Home 27 | }, 28 | { 29 | path: '/login', 30 | name: 'SignIn', 31 | component: SignIn 32 | }, 33 | { 34 | path: '/register', 35 | name: 'SignUp', 36 | component: SignUp 37 | }, 38 | { 39 | path: '/user', 40 | name: 'UserPage', 41 | component: UserPage 42 | }, 43 | { 44 | path: '/admin', 45 | name: 'AdminPage', 46 | component: AdminPage 47 | }, 48 | { 49 | path: '/email', 50 | name: 'EmailPage', 51 | component: EmailPage 52 | }, 53 | { 54 | path: '/registration-confirm/:token', 55 | name: 'RegistrationConfirmPage', 56 | component: RegistrationConfirmPage 57 | } 58 | ] 59 | }) -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | const state = { 7 | role: localStorage.getItem('user-role') || '', 8 | username: localStorage.getItem('user-name') || '', 9 | authorities: localStorage.getItem('authorities') || '', 10 | }; 11 | 12 | const getters = { 13 | isAuthenticated: state => { 14 | if (state.role != null && state.role != '') { 15 | return true; 16 | } else { 17 | return false; 18 | } 19 | }, 20 | isAdmin: state => { 21 | if (state.role === 'admin') { 22 | return true; 23 | } else { 24 | return false; 25 | } 26 | }, 27 | getUsername: state => { 28 | return state.username; 29 | }, 30 | getAuthorities: state => { 31 | return state.authorities; 32 | } 33 | }; 34 | 35 | const mutations = { 36 | auth_login: (state, user) => { 37 | localStorage.setItem('user-name', user.username); 38 | localStorage.setItem('user-authorities', user.roles); 39 | state.username = user.username; 40 | state.authorities = user.roles; 41 | var isUser = false; 42 | var isAdmin = false; 43 | for (var i = 0; i < user.roles.length; i++) { 44 | if (user.roles[i].authority === 'ROLE_USER') { 45 | isUser = true; 46 | } else if (user.roles[i].authority === 'ROLE_ADMIN') { 47 | isAdmin = true; 48 | } 49 | } 50 | if (isUser) { 51 | localStorage.setItem('user-role', 'user'); 52 | state.role = 'user'; 53 | } 54 | if (isAdmin) { 55 | localStorage.setItem('user-role', 'admin'); 56 | state.role = 'admin'; 57 | } 58 | }, 59 | auth_logout: () => { 60 | state.token = ''; 61 | state.role = ''; 62 | state.username = ''; 63 | state.authorities = []; 64 | localStorage.removeItem('user-role'); 65 | localStorage.removeItem('user-name'); 66 | localStorage.removeItem('user-authorities'); 67 | } 68 | }; 69 | 70 | const actions = { 71 | login: (context, user) => { 72 | context.commit('auth_login', user) 73 | }, 74 | logout: (context) => { 75 | context.commit('auth_logout'); 76 | } 77 | }; 78 | 79 | export const store = new Vuex.Store({ 80 | state, 81 | getters, 82 | mutations, 83 | actions 84 | }); -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | outputDir: 'build/dist', 3 | assetsDir: 'static', 4 | devServer: { 5 | host: 'localhost', 6 | port: 8081 7 | } 8 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vadim-emelianov/kotlin-spring-vue-gradle/b676513c21cb0f06b0d0e81d35a148a67ddac006/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=$((i+1)) 158 | done 159 | case $i in 160 | (0) set -- ;; 161 | (1) set -- "$args0" ;; 162 | (2) set -- "$args0" "$args1" ;; 163 | (3) set -- "$args0" "$args1" "$args2" ;; 164 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=$(save "$@") 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 184 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 185 | cd "$(dirname "$0")" 186 | fi 187 | 188 | exec "$JAVACMD" "$@" 189 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "demo" 2 | 3 | include(":frontend", ":backend") 4 | --------------------------------------------------------------------------------