Migrate from JDK 8 -> 11 and use Gradle instead of Maven

This commit is contained in:
William Brawner 2020-02-10 17:57:53 -07:00
parent 412c4b5edf
commit 8aa54d7029
14 changed files with 413 additions and 64 deletions

View file

@ -1,4 +1,4 @@
FROM openjdk:8-jdk as builder FROM openjdk:11-jdk as builder
MAINTAINER Billy Brawner <billy@wbrawner.com> MAINTAINER Billy Brawner <billy@wbrawner.com>
RUN groupadd --system --gid 1000 maven \ RUN groupadd --system --gid 1000 maven \
@ -11,8 +11,8 @@ COPY --chown=maven:maven . /home/maven/src
WORKDIR /home/maven/src WORKDIR /home/maven/src
RUN /home/maven/src/mvnw -DskipTests package RUN /home/maven/src/mvnw -DskipTests package
FROM openjdk:8-jdk-slim FROM openjdk:11-jdk-slim
EXPOSE 8080 EXPOSE 8080
COPY --from=builder /home/maven/src/target/budget-api.jar budget-api.jar COPY --from=builder /home/maven/src/target/budget-api.jar budget-api.jar
ENTRYPOINT ["/usr/local/openjdk-8/bin/java", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseCGroupMemoryLimitForHeap", "-XX:MaxRAMFraction=1", "-Xmx256m", "-jar", "/budget-api.jar"] ENTRYPOINT ["/usr/local/openjdk-11/bin/java", "-Xmx256M", "-jar", "/budget-api.jar"]

68
build.gradle.kts Normal file
View file

@ -0,0 +1,68 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.net.URI
buildscript {
repositories {
mavenLocal()
mavenCentral()
// maven {
// url = URI("https://repo.maven.apache.org/maven2")
// }
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61")
}
}
plugins {
java
kotlin("jvm") version "1.3.61"
id("org.springframework.boot") version "2.2.4.RELEASE"
}
apply(plugin = "io.spring.dependency-management")
repositories {
mavenLocal()
mavenCentral()
maven {
url = URI("http://repo.maven.apache.org/maven2")
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.session:spring-session-jdbc")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.61")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61")
implementation("io.springfox:springfox-swagger2:2.8.0")
implementation("io.springfox:springfox-swagger-ui:2.8.0")
runtimeOnly("mysql:mysql-connector-java:8.0.15")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test:5.1.5.RELEASE")
}
group = "com.wbrawner"
version = "0.0.1-SNAPSHOT"
description = "twigs-server"
sourceSets.getByName("main") {
java.srcDir("src/main/kotlin")
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "11"
}
val mainClass = "com.wbrawner.budgetserver.BudgetServerApplication"
tasks.bootJar {
mainClassName = mainClass
}
tasks.bootRun {
main = mainClass
}

183
gradlew vendored Executable file
View file

@ -0,0 +1,183 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

100
gradlew.bat vendored Normal file
View file

@ -0,0 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

5
settings.gradle Normal file
View file

@ -0,0 +1,5 @@
/*
* This file was generated by the Gradle 'init' task.
*/
rootProject.name = 'budget-server'

View file

@ -4,8 +4,11 @@ import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication import org.springframework.boot.runApplication
@SpringBootApplication @SpringBootApplication
class BudgetServerApplication open class BudgetServerApplication {
companion object {
fun main(args: Array<String>) { @JvmStatic
fun main(args: Array<String>) {
runApplication<BudgetServerApplication>(*args) runApplication<BudgetServerApplication>(*args)
}
}
} }

View file

@ -7,7 +7,6 @@ import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiOperation
import io.swagger.annotations.Authorization import io.swagger.annotations.Authorization
import org.hibernate.Hibernate import org.hibernate.Hibernate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import org.springframework.http.MediaType import org.springframework.http.MediaType
@ -18,15 +17,15 @@ import javax.transaction.Transactional
@RestController @RestController
@RequestMapping("/budgets") @RequestMapping("/budgets")
@Api(value = "Budgets", tags = ["Budgets"], authorizations = [Authorization("basic")]) @Api(value = "Budgets", tags = ["Budgets"], authorizations = [Authorization("basic")])
class BudgetController @Autowired constructor( @Transactional
open class BudgetController(
private val budgetRepository: BudgetRepository, private val budgetRepository: BudgetRepository,
private val transactionRepository: TransactionRepository, private val transactionRepository: TransactionRepository,
private val userRepository: UserRepository private val userRepository: UserRepository
) { ) {
@Transactional
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getBudgets", nickname = "getBudgets", tags = ["Budgets"]) @ApiOperation(value = "getBudgets", nickname = "getBudgets", tags = ["Budgets"])
fun getBudgets(page: Int?, count: Int?): ResponseEntity<List<BudgetResponse>> = ResponseEntity.ok( open fun getBudgets(page: Int?, count: Int?): ResponseEntity<List<BudgetResponse>> = ResponseEntity.ok(
budgetRepository.findAllByUsersContainsOrOwner( budgetRepository.findAllByUsersContainsOrOwner(
user = getCurrentUser()!!, user = getCurrentUser()!!,
pageable = PageRequest.of(page ?: 0, count ?: 1000, Sort.by("name"))) pageable = PageRequest.of(page ?: 0, count ?: 1000, Sort.by("name")))
@ -36,20 +35,18 @@ class BudgetController @Autowired constructor(
} }
) )
@Transactional
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getBudget", nickname = "getBudget", tags = ["Budgets"]) @ApiOperation(value = "getBudget", nickname = "getBudget", tags = ["Budgets"])
fun getBudget(@PathVariable id: Long): ResponseEntity<BudgetResponse> = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id) open fun getBudget(@PathVariable id: Long): ResponseEntity<BudgetResponse> = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id)
.orElse(null) .orElse(null)
?.let { ?.let {
Hibernate.initialize(it.users) Hibernate.initialize(it.users)
ResponseEntity.ok(BudgetResponse(it)) ResponseEntity.ok(BudgetResponse(it))
} ?: ResponseEntity.notFound().build() } ?: ResponseEntity.notFound().build()
@Transactional
@GetMapping("/{id}/balance", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/{id}/balance", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getBudgetBalance", nickname = "getBudgetBalance", tags = ["Budgets"]) @ApiOperation(value = "getBudgetBalance", nickname = "getBudgetBalance", tags = ["Budgets"])
fun getBudgetBalance(@PathVariable id: Long): ResponseEntity<BudgetBalanceResponse> = open fun getBudgetBalance(@PathVariable id: Long): ResponseEntity<BudgetBalanceResponse> =
budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id) budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id)
.orElse(null) .orElse(null)
?.let { ?.let {
@ -58,7 +55,7 @@ class BudgetController @Autowired constructor(
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newBudget", nickname = "newBudget", tags = ["Budgets"]) @ApiOperation(value = "newBudget", nickname = "newBudget", tags = ["Budgets"])
fun newBudget(@RequestBody request: NewBudgetRequest): ResponseEntity<BudgetResponse> { open fun newBudget(@RequestBody request: NewBudgetRequest): ResponseEntity<BudgetResponse> {
val users = request.userIds val users = request.userIds
.map { id -> userRepository.findById(id).orElse(null) } .map { id -> userRepository.findById(id).orElse(null) }
.filterNotNull() .filterNotNull()
@ -70,7 +67,7 @@ class BudgetController @Autowired constructor(
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "updateBudget", nickname = "updateBudget", tags = ["Budgets"]) @ApiOperation(value = "updateBudget", nickname = "updateBudget", tags = ["Budgets"])
fun updateBudget(@PathVariable id: Long, request: UpdateBudgetRequest): ResponseEntity<BudgetResponse> { open fun updateBudget(@PathVariable id: Long, request: UpdateBudgetRequest): ResponseEntity<BudgetResponse> {
var budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null) var budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
if (request.name != null) budget = budget.copy(name = request.name) if (request.name != null) budget = budget.copy(name = request.name)
@ -81,7 +78,7 @@ class BudgetController @Autowired constructor(
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE]) @DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
@ApiOperation(value = "deleteBudget", nickname = "deleteBudget", tags = ["Budgets"]) @ApiOperation(value = "deleteBudget", nickname = "deleteBudget", tags = ["Budgets"])
fun deleteBudget(@PathVariable id: Long): ResponseEntity<Unit> { open fun deleteBudget(@PathVariable id: Long): ResponseEntity<Unit> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null) val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, id).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
budgetRepository.delete(budget) budgetRepository.delete(budget)

View file

@ -3,34 +3,31 @@ package com.wbrawner.budgetserver.category
import com.wbrawner.budgetserver.ErrorResponse import com.wbrawner.budgetserver.ErrorResponse
import com.wbrawner.budgetserver.budget.BudgetRepository import com.wbrawner.budgetserver.budget.BudgetRepository
import com.wbrawner.budgetserver.getCurrentUser import com.wbrawner.budgetserver.getCurrentUser
import com.wbrawner.budgetserver.setToFirstOfMonth
import com.wbrawner.budgetserver.transaction.TransactionRepository import com.wbrawner.budgetserver.transaction.TransactionRepository
import io.swagger.annotations.Api import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiOperation
import io.swagger.annotations.Authorization import io.swagger.annotations.Authorization
import org.hibernate.Hibernate import org.hibernate.Hibernate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import java.lang.Integer.min import java.lang.Integer.min
import java.util.*
import javax.transaction.Transactional import javax.transaction.Transactional
@RestController @RestController
@RequestMapping("/categories") @RequestMapping("/categories")
@Api(value = "Categories", tags = ["Categories"], authorizations = [Authorization("basic")]) @Api(value = "Categories", tags = ["Categories"], authorizations = [Authorization("basic")])
class CategoryController @Autowired constructor( @Transactional
open class CategoryController(
private val budgetRepository: BudgetRepository, private val budgetRepository: BudgetRepository,
private val categoryRepository: CategoryRepository, private val categoryRepository: CategoryRepository,
private val transactionRepository: TransactionRepository private val transactionRepository: TransactionRepository
) { ) {
@Transactional
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getCategories", nickname = "getCategories", tags = ["Categories"]) @ApiOperation(value = "getCategories", nickname = "getCategories", tags = ["Categories"])
fun getCategories(budgetId: Long? = null, open fun getCategories(budgetId: Long? = null,
isExpense: Boolean? = null, isExpense: Boolean? = null,
count: Int?, count: Int?,
page: Int?, page: Int?,
@ -43,9 +40,9 @@ class CategoryController @Autowired constructor(
budgetRepository.findAllByUsersContainsOrOwner(getCurrentUser()!!) budgetRepository.findAllByUsersContainsOrOwner(getCurrentUser()!!)
}.toList() }.toList()
val pageRequest = PageRequest.of( val pageRequest = PageRequest.of(
min(0, page?.minus(1)?: 0), min(0, page?.minus(1) ?: 0),
count?: 1000, count ?: 1000,
Sort(sortOrder?: Sort.Direction.ASC, sortBy?: "title") Sort.by(sortOrder ?: Sort.Direction.ASC, sortBy ?: "title")
) )
val categories = if (isExpense == null) { val categories = if (isExpense == null) {
categoryRepository.findAllByBudgetIn(budgets, pageRequest) categoryRepository.findAllByBudgetIn(budgets, pageRequest)
@ -57,7 +54,7 @@ class CategoryController @Autowired constructor(
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getCategory", nickname = "getCategory", tags = ["Categories"]) @ApiOperation(value = "getCategory", nickname = "getCategory", tags = ["Categories"])
fun getCategory(@PathVariable id: Long): ResponseEntity<CategoryResponse> { open fun getCategory(@PathVariable id: Long): ResponseEntity<CategoryResponse> {
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null) budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
@ -66,7 +63,7 @@ class CategoryController @Autowired constructor(
@GetMapping("/{id}/balance", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/{id}/balance", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getCategoryBalance", nickname = "getCategoryBalance", tags = ["Categories"]) @ApiOperation(value = "getCategoryBalance", nickname = "getCategoryBalance", tags = ["Categories"])
fun getCategoryBalance(@PathVariable id: Long): ResponseEntity<CategoryBalanceResponse> { open fun getCategoryBalance(@PathVariable id: Long): ResponseEntity<CategoryBalanceResponse> {
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null) budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
@ -74,10 +71,9 @@ class CategoryController @Autowired constructor(
return ResponseEntity.ok(CategoryBalanceResponse(category.id, transactions)) return ResponseEntity.ok(CategoryBalanceResponse(category.id, transactions))
} }
@Transactional
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newCategory", nickname = "newCategory", tags = ["Categories"]) @ApiOperation(value = "newCategory", nickname = "newCategory", tags = ["Categories"])
fun newCategory(@RequestBody request: NewCategoryRequest): ResponseEntity<Any> { open fun newCategory(@RequestBody request: NewCategoryRequest): ResponseEntity<Any> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, request.budgetId).orElse(null) val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, request.budgetId).orElse(null)
?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID")) ?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID"))
Hibernate.initialize(budget.users) Hibernate.initialize(budget.users)
@ -91,7 +87,7 @@ class CategoryController @Autowired constructor(
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "updateCategory", nickname = "updateCategory", tags = ["Categories"]) @ApiOperation(value = "updateCategory", nickname = "updateCategory", tags = ["Categories"])
fun updateCategory(@PathVariable id: Long, @RequestBody request: UpdateCategoryRequest): ResponseEntity<CategoryResponse> { open fun updateCategory(@PathVariable id: Long, @RequestBody request: UpdateCategoryRequest): ResponseEntity<CategoryResponse> {
var category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() var category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null) budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
@ -103,7 +99,7 @@ class CategoryController @Autowired constructor(
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE]) @DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
@ApiOperation(value = "deleteCategory", nickname = "deleteCategory", tags = ["Categories"]) @ApiOperation(value = "deleteCategory", nickname = "deleteCategory", tags = ["Categories"])
fun deleteCategory(@PathVariable id: Long): ResponseEntity<Unit> { open fun deleteCategory(@PathVariable id: Long): ResponseEntity<Unit> {
val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() val category = categoryRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
val budget = budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null) val budget = budgetRepository.findByUsersContainsAndCategoriesContains(getCurrentUser()!!, category).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()

View file

@ -8,7 +8,7 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class JdbcUserDetailsService @Autowired open class JdbcUserDetailsService @Autowired
constructor(private val userRepository: UserRepository) : UserDetailsService { constructor(private val userRepository: UserRepository) : UserDetailsService {
@Throws(UsernameNotFoundException::class) @Throws(UsernameNotFoundException::class)

View file

@ -20,15 +20,15 @@ import javax.sql.DataSource
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
class SecurityConfig @Autowired open class SecurityConfig(
constructor(
private val env: Environment, private val env: Environment,
private val datasource: DataSource, private val datasource: DataSource,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val passwordResetRequestRepository: PasswordResetRequestRepository, private val passwordResetRequestRepository: PasswordResetRequestRepository,
private val userDetailsService: JdbcUserDetailsService) : WebSecurityConfigurerAdapter() { private val userDetailsService: JdbcUserDetailsService
) : WebSecurityConfigurerAdapter() {
val userDetailsManager: JdbcUserDetailsManager open val userDetailsManager: JdbcUserDetailsManager
@Bean @Bean
get() { get() {
val userDetailsManager = JdbcUserDetailsManager() val userDetailsManager = JdbcUserDetailsManager()
@ -36,14 +36,14 @@ constructor(
return userDetailsManager return userDetailsManager
} }
val authenticationProvider: DaoAuthenticationProvider open val authenticationProvider: DaoAuthenticationProvider
@Bean @Bean
get() = DaoAuthenticationProvider().apply { get() = DaoAuthenticationProvider().apply {
this.setPasswordEncoder(passwordEncoder) this.setPasswordEncoder(passwordEncoder)
this.setUserDetailsService(userDetailsService) this.setUserDetailsService(userDetailsService)
} }
val passwordEncoder: PasswordEncoder open val passwordEncoder: PasswordEncoder
@Bean @Bean
get() = BCryptPasswordEncoder() get() = BCryptPasswordEncoder()
@ -70,5 +70,5 @@ constructor(
@Configuration @Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) @EnableGlobalMethodSecurity(prePostEnabled = true)
class MethodSecurity : GlobalMethodSecurityConfiguration() open class MethodSecurity : GlobalMethodSecurityConfiguration()

View file

@ -12,9 +12,9 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2
@Configuration @Configuration
@EnableSwagger2 @EnableSwagger2
class SwaggerConfig : WebMvcConfigurationSupport() { open class SwaggerConfig : WebMvcConfigurationSupport() {
@Bean @Bean
fun budgetApi(): Docket = Docket(DocumentationType.SWAGGER_2) open fun budgetApi(): Docket = Docket(DocumentationType.SWAGGER_2)
.securitySchemes(mutableListOf(BasicAuth("basic"))) .securitySchemes(mutableListOf(BasicAuth("basic")))
.select() .select()
.apis(RequestHandlerSelectors.basePackage("com.wbrawner.budgetserver")) .apis(RequestHandlerSelectors.basePackage("com.wbrawner.budgetserver"))

View file

@ -26,17 +26,17 @@ import javax.transaction.Transactional
@RestController @RestController
@RequestMapping("/transactions") @RequestMapping("/transactions")
@Api(value = "Transactions", tags = ["Transactions"], authorizations = [Authorization("basic")]) @Api(value = "Transactions", tags = ["Transactions"], authorizations = [Authorization("basic")])
class TransactionController @Autowired constructor( @Transactional
open class TransactionController(
private val budgetRepository: BudgetRepository, private val budgetRepository: BudgetRepository,
private val categoryRepository: CategoryRepository, private val categoryRepository: CategoryRepository,
private val transactionRepository: TransactionRepository private val transactionRepository: TransactionRepository
) { ) {
private val logger = LoggerFactory.getLogger(TransactionController::class.java) private val logger = LoggerFactory.getLogger(TransactionController::class.java)
@Transactional
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getTransactions", nickname = "getTransactions", tags = ["Transactions"]) @ApiOperation(value = "getTransactions", nickname = "getTransactions", tags = ["Transactions"])
fun getTransactions( open fun getTransactions(
@RequestParam("categoryId") categoryIds: Array<Long>? = null, @RequestParam("categoryId") categoryIds: Array<Long>? = null,
@RequestParam("budgetId") budgetIds: Array<Long>? = null, @RequestParam("budgetId") budgetIds: Array<Long>? = null,
@RequestParam("from") from: String? = null, @RequestParam("from") from: String? = null,
@ -90,17 +90,16 @@ class TransactionController @Autowired constructor(
@GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/{id}", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getTransaction", nickname = "getTransaction", tags = ["Transactions"]) @ApiOperation(value = "getTransaction", nickname = "getTransaction", tags = ["Transactions"])
fun getTransaction(@PathVariable id: Long): ResponseEntity<TransactionResponse> { open fun getTransaction(@PathVariable id: Long): ResponseEntity<TransactionResponse> {
val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null) budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
return ResponseEntity.ok(TransactionResponse(transaction)) return ResponseEntity.ok(TransactionResponse(transaction))
} }
@Transactional
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newTransaction", nickname = "newTransaction", tags = ["Transactions"]) @ApiOperation(value = "newTransaction", nickname = "newTransaction", tags = ["Transactions"])
fun newTransaction(@RequestBody request: NewTransactionRequest): ResponseEntity<Any> { open fun newTransaction(@RequestBody request: NewTransactionRequest): ResponseEntity<Any> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, request.budgetId).orElse(null) val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, request.budgetId).orElse(null)
?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID")) ?: return ResponseEntity.badRequest().body(ErrorResponse("Invalid budget ID"))
Hibernate.initialize(budget.users) Hibernate.initialize(budget.users)
@ -121,7 +120,7 @@ class TransactionController @Autowired constructor(
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "updateTransaction", nickname = "updateTransaction", tags = ["Transactions"]) @ApiOperation(value = "updateTransaction", nickname = "updateTransaction", tags = ["Transactions"])
fun updateTransaction(@PathVariable id: Long, @RequestBody request: UpdateTransactionRequest): ResponseEntity<TransactionResponse> { open fun updateTransaction(@PathVariable id: Long, @RequestBody request: UpdateTransactionRequest): ResponseEntity<TransactionResponse> {
var transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() var transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
var budget = budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction) var budget = budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction)
.orElse(null) ?: return ResponseEntity.notFound().build() .orElse(null) ?: return ResponseEntity.notFound().build()
@ -146,7 +145,7 @@ class TransactionController @Autowired constructor(
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE]) @DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
@ApiOperation(value = "deleteTransaction", nickname = "deleteTransaction", tags = ["Transactions"]) @ApiOperation(value = "deleteTransaction", nickname = "deleteTransaction", tags = ["Transactions"])
fun deleteTransaction(@PathVariable id: Long): ResponseEntity<Unit> { open fun deleteTransaction(@PathVariable id: Long): ResponseEntity<Unit> {
val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build() val transaction = transactionRepository.findById(id).orElse(null) ?: return ResponseEntity.notFound().build()
// Check that the transaction belongs to an budget that the user has access to before deleting it // Check that the transaction belongs to an budget that the user has access to before deleting it
budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null) budgetRepository.findByUsersContainsAndTransactionsContains(getCurrentUser()!!, transaction).orElse(null)

View file

@ -21,17 +21,17 @@ import javax.transaction.Transactional
@RestController @RestController
@RequestMapping("/users") @RequestMapping("/users")
@Api(value = "Users", tags = ["Users"], authorizations = [Authorization("basic")]) @Api(value = "Users", tags = ["Users"], authorizations = [Authorization("basic")])
class UserController @Autowired constructor( @Transactional
open class UserController(
private val budgetRepository: BudgetRepository, private val budgetRepository: BudgetRepository,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder, private val passwordEncoder: PasswordEncoder,
private val authenticationProvider: DaoAuthenticationProvider private val authenticationProvider: DaoAuthenticationProvider
) { ) {
@Transactional
@GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getUsers", nickname = "getUsers", tags = ["Users"]) @ApiOperation(value = "getUsers", nickname = "getUsers", tags = ["Users"])
fun getUsers(budgetId: Long): ResponseEntity<List<UserResponse>> { open fun getUsers(budgetId: Long): ResponseEntity<List<UserResponse>> {
val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, budgetId).orElse(null) val budget = budgetRepository.findByUsersContainsAndId(getCurrentUser()!!, budgetId).orElse(null)
?: return ResponseEntity.notFound().build() ?: return ResponseEntity.notFound().build()
Hibernate.initialize(budget.users) Hibernate.initialize(budget.users)
@ -40,7 +40,7 @@ class UserController @Autowired constructor(
@PostMapping("/login", produces = [MediaType.APPLICATION_JSON_VALUE]) @PostMapping("/login", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "login", nickname = "login", tags = ["Users"]) @ApiOperation(value = "login", nickname = "login", tags = ["Users"])
fun login(@RequestBody request: LoginRequest): ResponseEntity<UserResponse> { open fun login(@RequestBody request: LoginRequest): ResponseEntity<UserResponse> {
val authReq = UsernamePasswordAuthenticationToken(request.username, request.password) val authReq = UsernamePasswordAuthenticationToken(request.username, request.password)
val auth = try { val auth = try {
authenticationProvider.authenticate(authReq) authenticationProvider.authenticate(authReq)
@ -53,21 +53,20 @@ class UserController @Autowired constructor(
@GetMapping("/me", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/me", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "getProfile", nickname = "getProfile", tags = ["Users"]) @ApiOperation(value = "getProfile", nickname = "getProfile", tags = ["Users"])
fun getProfile(): ResponseEntity<UserResponse> { open fun getProfile(): ResponseEntity<UserResponse> {
val user = getCurrentUser()?: return ResponseEntity.status(401).build() val user = getCurrentUser()?: return ResponseEntity.status(401).build()
return ResponseEntity.ok(UserResponse(user)) return ResponseEntity.ok(UserResponse(user))
} }
@Transactional
@GetMapping("/search", produces = [MediaType.APPLICATION_JSON_VALUE]) @GetMapping("/search", produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "searchUsers", nickname = "searchUsers", tags = ["Users"]) @ApiOperation(value = "searchUsers", nickname = "searchUsers", tags = ["Users"])
fun searchUsers(query: String): ResponseEntity<List<UserResponse>> { open fun searchUsers(query: String): ResponseEntity<List<UserResponse>> {
return ResponseEntity.ok(userRepository.findByNameContains(query).map { UserResponse(it) }) return ResponseEntity.ok(userRepository.findByNameContains(query).map { UserResponse(it) })
} }
@GetMapping("/{id}") @GetMapping("/{id}")
@ApiOperation(value = "getUser", nickname = "getUser", tags = ["Users"]) @ApiOperation(value = "getUser", nickname = "getUser", tags = ["Users"])
fun getUser(@PathVariable id: Long): ResponseEntity<UserResponse> = userRepository.findById(id).orElse(null) open fun getUser(@PathVariable id: Long): ResponseEntity<UserResponse> = userRepository.findById(id).orElse(null)
?.let { ?.let {
ResponseEntity.ok(UserResponse(it)) ResponseEntity.ok(UserResponse(it))
} }
@ -75,7 +74,7 @@ class UserController @Autowired constructor(
@PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PostMapping("/new", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "newUser", nickname = "newUser", tags = ["Users"]) @ApiOperation(value = "newUser", nickname = "newUser", tags = ["Users"])
fun newUser(@RequestBody request: NewUserRequest): ResponseEntity<Any> { open fun newUser(@RequestBody request: NewUserRequest): ResponseEntity<Any> {
if (userRepository.findByName(request.username).isPresent) if (userRepository.findByName(request.username).isPresent)
return ResponseEntity.badRequest() return ResponseEntity.badRequest()
.body(ErrorResponse("Username taken")) .body(ErrorResponse("Username taken"))
@ -94,7 +93,7 @@ class UserController @Autowired constructor(
@PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]) @PutMapping("/{id}", consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE])
@ApiOperation(value = "updateUser", nickname = "updateUser", tags = ["Users"]) @ApiOperation(value = "updateUser", nickname = "updateUser", tags = ["Users"])
fun updateUser(@PathVariable id: Long, @RequestBody request: UpdateUserRequest): ResponseEntity<Any> { open fun updateUser(@PathVariable id: Long, @RequestBody request: UpdateUserRequest): ResponseEntity<Any> {
if (getCurrentUser()!!.id != id) return ResponseEntity.status(403) if (getCurrentUser()!!.id != id) return ResponseEntity.status(403)
.body(ErrorResponse("Attempting to modify another user's budget")) .body(ErrorResponse("Attempting to modify another user's budget"))
var user = userRepository.findById(getCurrentUser()!!.id!!).orElse(null)?: return ResponseEntity.notFound().build() var user = userRepository.findById(getCurrentUser()!!.id!!).orElse(null)?: return ResponseEntity.notFound().build()
@ -115,7 +114,7 @@ class UserController @Autowired constructor(
@DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE]) @DeleteMapping("/{id}", produces = [MediaType.TEXT_PLAIN_VALUE])
@ApiOperation(value = "deleteUser", nickname = "deleteUser", tags = ["Users"]) @ApiOperation(value = "deleteUser", nickname = "deleteUser", tags = ["Users"])
fun deleteUser(@PathVariable id: Long): ResponseEntity<Unit> { open fun deleteUser(@PathVariable id: Long): ResponseEntity<Unit> {
if(getCurrentUser()!!.id != id) return ResponseEntity.status(403).build() if(getCurrentUser()!!.id != id) return ResponseEntity.status(403).build()
userRepository.deleteById(id) userRepository.deleteById(id)
return ResponseEntity.ok().build() return ResponseEntity.ok().build()

View file

@ -4,7 +4,6 @@ spring.datasource.username=budget
spring.datasource.password=budget spring.datasource.password=budget
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.profiles.active=prod spring.profiles.active=prod
spring.session.store-type=jdbc
spring.session.jdbc.initialize-schema=always spring.session.jdbc.initialize-schema=always
spring.datasource.testWhileIdle=true spring.datasource.testWhileIdle=true
spring.datasource.timeBetweenEvictionRunsMillis=60000 spring.datasource.timeBetweenEvictionRunsMillis=60000