├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── kotlin └── br │ └── com │ └── alura │ └── leilaoapi │ ├── LeilaoApiApplication.kt │ ├── api │ └── controller │ │ ├── ConstantesController.kt │ │ ├── LeilaoController.kt │ │ ├── LeilaoTestController.kt │ │ └── ResetController.kt │ ├── controller │ └── MainController.kt │ ├── exception │ ├── LanceMenorQueUltimoLanceException.kt │ ├── MesmoUsuarioDoUltimoLance.kt │ └── UsuarioJaDeuCincoLances.kt │ ├── model │ ├── Lance.kt │ ├── Leilao.kt │ └── Usuario.kt │ ├── repository │ └── LeilaoRepository.kt │ └── service │ ├── LeilaoService.kt │ └── ResetService.kt └── resources ├── application-producao.yml ├── application-teste.yml ├── application.yml └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | /out/ 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | 28 | ### Meus arquivos ### 29 | /database/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## API de leilões públicos 2 | 3 | Essa é uma aplicação web em Spring Boot destinada ao curso de testes no Android com Mocks. O objetivo dela é permitir o cadastro de leilões e manter os dados de lances. O projeto foi desenvolvido com base no Gradle utilizando Kotlin e Java 8. 4 | 5 | ## Como importar e modificar o código 6 | 7 | > Para modificar o código, recomendo que utilize o IntelliJ (pode ser o Community), pois ele já vem com o plugin do Gradle por padrão. Fique à vontade em usar sua ferramenta preferida, certifique-se que ela dê suporte ao Gradle. Evite o uso do Gradle instalado no seu computador, utilize o [Gradle Wrapper](https://medium.com/collabcode/gradle-nativo-ou-wrapper-saiba-qual-utilizar-e029058bf80) para que mantenha a mesma versão do build. 8 | 9 | Ao clonar o projeto, você pode importá-lo como um projeto Gradle, siga as orientações da IDE até que o build seja finalizado. Todo código fonte vai estar acessível no diretório **src/main/kotlin**. 10 | 11 | ## Executando o projeto 12 | 13 | O projeto pode ser executado a partir da função `main` do arquivo `src/main/kotlin/br/com/alura/leilaoapi/LeilaoApiApplication.kt`, por padrão, ela ficará disponível na porta 8080 sendo acessível via endereço http://localhost:8080/. 14 | 15 | Caso tenha interesse em modificar a porta, vá no arquivo **/src/main/resources/application.yml** e altere o valor da propriedade `server.port`, segue o exemplo: 16 | 17 | ```yml 18 | server: 19 | port: ${port:8081} 20 | ``` 21 | 22 | Nessa amostra a execução do servidor vai ser feita na porta 8081. 23 | 24 | ## Mapeamentos disponíveis 25 | 26 | Por padrão, o projeto provê três mapeamentos. 27 | 28 | ### Páginas HTML 29 | 30 | - **GET** `/` -> Essa é a rota inicial na qual é apresentada a página que permite cadastrar os leilões e listá-los com os seus IDs. 31 | 32 | ### API REST 33 | 34 | - **GET** `/leilao` -> Devolve a lista com todos os leilões cadastrados e seus lances. 35 | 36 | - **PUT** `/leilao/{id}/lance` -> Permite a adição de novos lances com base no id do leilão existente, recebendo o lance via corpo da requisição. O objeto para o lance pode ser enviado com a seguinte estrutura. 37 | 38 | ```json 39 | { 40 | "usuario" : { 41 | "id": 1, 42 | "nome" : "Alex" 43 | }, 44 | "valor" : 1520.0 45 | } 46 | ``` 47 | 48 | Para mais detalhes de implementação, consulte o código dos controllers da aplicação. 49 | 50 | ## Perfis do servidor 51 | 52 | Ao executar o servidor, é possível ajustar o perfil para simular ambientes entre produção e teste. Para alternar o perfil em desenvolvimento, basta apenas modificar a propriedade **spring.profiles.active**. Por padrão, está configurado o perfil `producao` que pode ser alternado para o perfil `teste`. 53 | 54 | Na execução do arquivo **jar** é possível alternar o perfil utilizando o argumento `-Dspring.profiles.active`. Ao executar sem envio de argumentos, o perfil `producao` será ativado automaticamente. Se o perfil `teste` for desejado, basta executar da seguinte maneira: 55 | 56 | ``` 57 | java -jar -Dspring.profiles.active=teste nomeDoArquivo.jar 58 | ``` 59 | 60 | ## Detalhes do perfil de teste 61 | 62 | No perfil de teste, o banco de dados opera em memória, ou seja, é uma solução que mantém os dados enquanto o servidor estiver rodando. Dessa forma, ele não compromete o ambiente de produção. Este perfil é destinado a testes que exijam a necessidade de integração com a API. 63 | 64 | ### Mapeamentos exclusivos para o ambiente de teste 65 | 66 | Visando a flexibilidade de testes, no perfil de `teste` foram adicionados os seguintes mapeamentos: 67 | 68 | ### Cadastro de leilão 69 | 70 | - **POST** `/leilao` -> Recebe um objeto do tipo `Leilao` e devolve o mesmo leilão com o `id` que foi salvo, segue o modelo de requisição: 71 | 72 | ```json 73 | { 74 | "descricao" : "Console" 75 | } 76 | ``` 77 | 78 | ### Limpando banco de dados 79 | 80 | - **GET** `/reset` -> Limpa o banco de dados. Caso a requisição seja sucedida, é devolvida a resposta com a mensagem `"Banco de dados limpado"`. 81 | 82 | ## Dúvidas sobre o Spring Boot 83 | 84 | Caso não conheça o mínimo do Spring Boot com Kotlin e tenha interesse em aprender, sugiro os conteúdos que mantenho para a comunidade que estão acessíveis nestes links: 85 | 86 | - [CRUD API parte 1](https://medium.com/collabcode/implementando-uma-crud-api-no-spring-boot-com-kotlin-parte-1-c6e281d0f8f8); 87 | 88 | - [CRUD API parte 2](https://medium.com/collabcode/implementando-uma-crud-api-no-spring-boot-com-kotlin-parte-2-3346312dc956); 89 | 90 | - [Boas práticas para API com Spring](https://medium.com/collabcode/boas-pr%C3%A1ticas-para-a-implementa%C3%A7%C3%A3o-de-apis-no-spring-boot-com-kotlin-6e77aac110da). 91 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | kotlinVersion = '1.2.51' 4 | springBootVersion = '2.0.3.RELEASE' 5 | } 6 | repositories { 7 | mavenCentral() 8 | } 9 | dependencies { 10 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 11 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") 12 | classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") 13 | } 14 | } 15 | 16 | apply plugin: 'kotlin' 17 | apply plugin: 'kotlin-spring' 18 | apply plugin: 'eclipse' 19 | apply plugin: 'org.springframework.boot' 20 | apply plugin: 'io.spring.dependency-management' 21 | 22 | group = 'br.com.alura' 23 | version = '0.0.1-SNAPSHOT' 24 | sourceCompatibility = 1.8 25 | compileKotlin { 26 | kotlinOptions { 27 | freeCompilerArgs = ["-Xjsr305=strict"] 28 | jvmTarget = "1.8" 29 | } 30 | } 31 | compileTestKotlin { 32 | kotlinOptions { 33 | freeCompilerArgs = ["-Xjsr305=strict"] 34 | jvmTarget = "1.8" 35 | } 36 | } 37 | 38 | repositories { 39 | mavenCentral() 40 | } 41 | 42 | 43 | dependencies { 44 | compile('org.springframework.boot:spring-boot-starter-data-jpa') 45 | compile('org.springframework.boot:spring-boot-starter-web') 46 | compile('com.fasterxml.jackson.module:jackson-module-kotlin') 47 | compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 48 | compile("org.jetbrains.kotlin:kotlin-reflect") 49 | runtime('org.springframework.boot:spring-boot-devtools') 50 | compile('org.springframework.boot:spring-boot-starter-thymeleaf') 51 | runtime('org.hsqldb:hsqldb') 52 | testCompile('org.springframework.boot:spring-boot-starter-test') 53 | } 54 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alura-cursos/leilao-api-android-com-mocks/662f46a47c02c8f9b5bda9f493622960dad343ad/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 05 09:21:14 BRT 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'leilao-api' 2 | -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/LeilaoApiApplication.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication 4 | import org.springframework.boot.runApplication 5 | 6 | @SpringBootApplication 7 | class LeilaoApiApplication 8 | 9 | fun main(args: Array) { 10 | runApplication(*args) 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/api/controller/ConstantesController.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.api.controller 2 | 3 | const val PERFIL_TESTE = "teste" 4 | const val ERRO_GENERICO = "Não foi possível propor o lance" -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/api/controller/LeilaoController.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.api.controller 2 | 3 | import br.com.alura.leilaoapi.exception.LanceMenorQueUltimoLanceException 4 | import br.com.alura.leilaoapi.exception.MesmoUsuarioDoUltimoLance 5 | import br.com.alura.leilaoapi.exception.UsuarioJaDeuCincoLances 6 | import br.com.alura.leilaoapi.model.Lance 7 | import br.com.alura.leilaoapi.model.Leilao 8 | import br.com.alura.leilaoapi.service.LeilaoService 9 | import org.springframework.http.ResponseEntity 10 | import org.springframework.web.bind.annotation.* 11 | import org.springframework.web.servlet.ModelAndView 12 | 13 | @RestController 14 | @RequestMapping("leilao") 15 | class LeilaoController( 16 | private val service: LeilaoService) { 17 | 18 | @PutMapping("{id}/lance") 19 | fun propoeLance(@PathVariable("id") id: Long, @RequestBody lance: Lance): ResponseEntity { 20 | return try { 21 | val leilaoRetornado = service.buscaPorId(id).get() 22 | service.salvaNovoLance(lance, leilaoRetornado) 23 | ResponseEntity.ok(leilaoRetornado) 24 | } catch (e: Exception) { 25 | val mensagemDeErro = when (e) { 26 | is NoSuchElementException, is LanceMenorQueUltimoLanceException, 27 | is MesmoUsuarioDoUltimoLance, is UsuarioJaDeuCincoLances -> e.message 28 | else -> ERRO_GENERICO 29 | } 30 | ResponseEntity.badRequest().body(mensagemDeErro) 31 | } 32 | } 33 | 34 | @GetMapping 35 | fun todos(): ResponseEntity> { 36 | val todosLeiloes = service.todos() 37 | return ResponseEntity.ok(todosLeiloes) 38 | } 39 | 40 | @PostMapping("form") 41 | fun novo(leilao: Leilao): ModelAndView { 42 | ResponseEntity.ok(service.salva(leilao)) 43 | return ModelAndView("redirect:/") 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/api/controller/LeilaoTestController.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.api.controller 2 | 3 | import br.com.alura.leilaoapi.model.Leilao 4 | import br.com.alura.leilaoapi.service.LeilaoService 5 | import org.springframework.context.annotation.Profile 6 | import org.springframework.http.ResponseEntity 7 | import org.springframework.stereotype.Controller 8 | import org.springframework.web.bind.annotation.PostMapping 9 | import org.springframework.web.bind.annotation.RequestBody 10 | import org.springframework.web.bind.annotation.RequestMapping 11 | 12 | @Controller 13 | @RequestMapping("leilao") 14 | @Profile(PERFIL_TESTE) 15 | class LeilaoTestController( 16 | private val service: LeilaoService) { 17 | 18 | @PostMapping 19 | @Profile(PERFIL_TESTE) 20 | fun novo(@RequestBody leilao: Leilao): ResponseEntity { 21 | val leilaoSalvo = service.salva(leilao) 22 | return ResponseEntity.ok(leilaoSalvo) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/api/controller/ResetController.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.api.controller 2 | 3 | import br.com.alura.leilaoapi.service.ResetService 4 | import org.springframework.context.annotation.Profile 5 | import org.springframework.http.ResponseEntity 6 | import org.springframework.stereotype.Controller 7 | import org.springframework.web.bind.annotation.GetMapping 8 | import org.springframework.web.bind.annotation.RequestMapping 9 | 10 | private const val BANCO_LIMPADO_COM_SUCESSO = "Banco de dados limpado" 11 | 12 | @Controller 13 | @RequestMapping("reset") 14 | @Profile(PERFIL_TESTE) 15 | class ResetController( 16 | private val resetService: ResetService) { 17 | 18 | @GetMapping 19 | fun reset(): ResponseEntity { 20 | resetService.limpaBancoDeDados() 21 | return ResponseEntity.ok(BANCO_LIMPADO_COM_SUCESSO) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/controller/MainController.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.controller 2 | 3 | import br.com.alura.leilaoapi.service.LeilaoService 4 | import org.springframework.stereotype.Controller 5 | import org.springframework.web.bind.annotation.GetMapping 6 | import org.springframework.web.bind.annotation.ModelAttribute 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | 9 | @Controller 10 | @RequestMapping("/") 11 | class MainController( 12 | private val leilaoService: LeilaoService) { 13 | 14 | @ModelAttribute("leiloes") 15 | fun leiloes(): List = { 16 | val todosLeiloes = leilaoService.todos() 17 | todosLeiloes.map { "${it.id} - ${it.descricao}" } 18 | }() 19 | 20 | @GetMapping 21 | fun index(): String { 22 | return "index" 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/exception/LanceMenorQueUltimoLanceException.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.exception 2 | 3 | class LanceMenorQueUltimoLanceException : RuntimeException(){ 4 | override val message: String 5 | get() = "Lance menor que o último lance" 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/exception/MesmoUsuarioDoUltimoLance.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.exception 2 | 3 | class MesmoUsuarioDoUltimoLance : RuntimeException() { 4 | override val message: String 5 | get() = "Mesmo usuário do último lance" 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/exception/UsuarioJaDeuCincoLances.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.exception 2 | 3 | class UsuarioJaDeuCincoLances : RuntimeException() { 4 | override val message: String 5 | get() = "Usuário já deu cinco lances" 6 | } 7 | -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/model/Lance.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.model 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import org.hibernate.annotations.Cascade 5 | import org.hibernate.annotations.CascadeType 6 | import javax.persistence.* 7 | 8 | @Entity 9 | data class Lance( 10 | @Id 11 | @GeneratedValue(strategy = GenerationType.IDENTITY) 12 | @JsonProperty(value = "id", access = JsonProperty.Access.READ_ONLY) 13 | val id: Long = 0, 14 | @ManyToOne 15 | @Cascade(CascadeType.ALL) 16 | val usuario: Usuario = Usuario(), 17 | val valor: Double = 0.0) -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/model/Leilao.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.model 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import javax.persistence.* 5 | 6 | private const val VALOR_PADRAO = 0.0 7 | 8 | @Entity 9 | data class Leilao( 10 | @Id 11 | @GeneratedValue(strategy = GenerationType.IDENTITY) 12 | @JsonProperty(value = "id", access = JsonProperty.Access.READ_ONLY) 13 | val id: Long = 0, 14 | val descricao: String = "", 15 | @OneToMany(cascade = [CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE]) 16 | val lances: MutableList = mutableListOf()) { 17 | 18 | fun maiorLance() = lances.maxBy { it.valor }?.valor ?: VALOR_PADRAO 19 | 20 | fun adiciona(lance: Lance) { 21 | lances.add(0, lance) 22 | } 23 | 24 | fun ultimoLance(): Lance { 25 | return lances.first() 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/model/Usuario.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.model 2 | 3 | import java.io.Serializable 4 | import javax.persistence.Entity 5 | import javax.persistence.Id 6 | import javax.persistence.IdClass 7 | 8 | @Entity 9 | @IdClass(IdUsuario::class) 10 | data class Usuario( 11 | @Id 12 | val id: Long = 0, 13 | @Id 14 | val nome: String = "") 15 | 16 | private class IdUsuario : Serializable { 17 | val id: Long = 0 18 | val nome: String = "" 19 | } 20 | -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/repository/LeilaoRepository.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.repository 2 | 3 | import br.com.alura.leilaoapi.model.Leilao 4 | import org.springframework.data.repository.PagingAndSortingRepository 5 | 6 | interface LeilaoRepository : PagingAndSortingRepository -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/service/LeilaoService.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.service 2 | 3 | import br.com.alura.leilaoapi.exception.LanceMenorQueUltimoLanceException 4 | import br.com.alura.leilaoapi.exception.MesmoUsuarioDoUltimoLance 5 | import br.com.alura.leilaoapi.exception.UsuarioJaDeuCincoLances 6 | import br.com.alura.leilaoapi.model.Lance 7 | import br.com.alura.leilaoapi.model.Leilao 8 | import br.com.alura.leilaoapi.repository.LeilaoRepository 9 | import org.springframework.stereotype.Service 10 | import java.util.* 11 | 12 | private const val LIMITE_LANCES: Int = 4 13 | 14 | @Service 15 | class LeilaoService( 16 | val repository: LeilaoRepository) { 17 | 18 | fun todos(): List { 19 | Leilao() 20 | return repository.findAll().toList() 21 | } 22 | 23 | fun buscaPorId(id: Long): Optional { 24 | return repository.findById(id) 25 | } 26 | 27 | fun salvaNovoLance(lance: Lance, leilao: Leilao) { 28 | validaLance(leilao, lance) 29 | leilao.adiciona(lance) 30 | repository.save(leilao) 31 | } 32 | 33 | fun salva(leilao: Leilao): Leilao { 34 | return repository.save(leilao) 35 | } 36 | 37 | private fun validaLance(leilao: Leilao, lance: Lance) { 38 | if (leilao.lances.isNotEmpty()) { 39 | if (menorQueUltimoLance(lance, leilao)) 40 | throw LanceMenorQueUltimoLanceException() 41 | if (mesmoUsuario(lance, leilao)) 42 | throw MesmoUsuarioDoUltimoLance() 43 | if (temCincoLances(leilao, lance)) 44 | throw UsuarioJaDeuCincoLances() 45 | } 46 | } 47 | 48 | private fun temCincoLances(leilao: Leilao, lance: Lance) = 49 | leilao.lances.count { it.usuario.id == lance.usuario.id } > LIMITE_LANCES 50 | 51 | private fun menorQueUltimoLance(lance: Lance, leilao: Leilao) = 52 | lance.valor < leilao.maiorLance() 53 | 54 | private fun mesmoUsuario(lance: Lance, leilao: Leilao) = 55 | lance.usuario == leilao.ultimoLance().usuario 56 | 57 | } -------------------------------------------------------------------------------- /src/main/kotlin/br/com/alura/leilaoapi/service/ResetService.kt: -------------------------------------------------------------------------------- 1 | package br.com.alura.leilaoapi.service 2 | 3 | import org.springframework.stereotype.Service 4 | import javax.persistence.EntityManager 5 | import javax.transaction.Transactional 6 | 7 | @Service 8 | class ResetService( 9 | private val em: EntityManager) { 10 | 11 | @Transactional 12 | fun limpaBancoDeDados() { 13 | val limpaTodasTabelas = "TRUNCATE SCHEMA PUBLIC RESTART IDENTITY " + 14 | "AND COMMIT NO CHECK" 15 | val query = em.createNativeQuery(limpaTodasTabelas) 16 | query.executeUpdate() 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/main/resources/application-producao.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:hsqldb:file:database/main/db 4 | server: 5 | port: ${port:8080} -------------------------------------------------------------------------------- /src/main/resources/application-teste.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:hsqldb:mem:db-teste 4 | server: 5 | port: ${port:8081} -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: producao 4 | datasource: 5 | username: sa 6 | driver-class-name: org.hsqldb.jdbc.JDBCDriver 7 | jpa: 8 | properties: 9 | hibernate: 10 | dialect: org.hibernate.dialect.HSQLDialect 11 | event: 12 | merge: 13 | entity_copy_observer: allow 14 | hibernate: 15 | ddl-auto: update 16 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Api de leilões públicos 6 | 7 | 8 |

Api de leilões públicos

9 | 10 |

Cadastre o leilão

11 |
12 | 13 | 14 |
15 | 16 |
17 |

Todos os leilões

18 |
19 |

20 |

21 |
22 | 23 | 24 | --------------------------------------------------------------------------------