diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..549e00a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..d58dfb70 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/README.md b/README.md index d45458df..ffb73878 100644 --- a/README.md +++ b/README.md @@ -54,23 +54,37 @@ Criar um cadastro de veículos com os seguintes campos: - **Documentação:** Explique no README os benefícios de usar GraphQL no contexto do projeto, descrevendo também como configurar e rodar o BFF localmente. - **Questões:** Além da implementação, responda às seguintes perguntas no README: - **Pergunta 1**: Explique o que é o GraphQL e como ele se diferencia de uma API REST tradicional. + - O graphQL se trata de uma linguagem de consulta para sua API, ele permite que o cliente solicite apenas os dados que ele precisa, e nada mais, enquanto uma API REST tradicional retorna todos os dados de uma vez. - **Pergunta 2**: Descreva como você implementaria o uso do GraphQL como BFF (Backend for Frontend) neste projeto de gerenciamento de estacionamento. Forneça exemplos práticos. + - Se tratando de um BFF eu faria uma api separada com o objetivo de ser meu gateway onde nela eu realizaria a chamada para as apis posteriores e retornaria o resultado atraves dela. - **Pergunta 3**: Quais são os benefícios de utilizar GraphQL em relação à flexibilidade das consultas? Cite possíveis desafios ao utilizá-lo. + - Identifiquei que inicialmente teriamos que planejar bem a modelagem dos dados, para que a consulta seja eficiente, e alem disso a curva de aprendizado para quem nao conhece graphQL pode ser um desafio. ### 2. Banco de Dados (Nível Básico) - **Pergunta 1**: Explique os principais conceitos de um banco de dados relacional, como tabelas, chaves primárias e estrangeiras. + - banco de dados reacinal possuem tabelas para criar uma representacao dos dados, as chaves primarias sao utilizadas para identificar unicamente um registro na tabela, as chaves estrangeiras sao utilizadas para relacionar tabela - **Pergunta 2**: No contexto de uma aplicação de gerenciamento de estacionamento, como você organizaria a modelagem de dados para suportar as funcionalidades de controle de entrada e saída de veículos? + - Como se trata de uma aplicacao de gerenciamento de estacionamento, eu criaria uma tabela para cada entidade, estabelecimento, veiculos, controle de entrada e saida, e faria relacionamento entre elas atraves de chaves estrangeiras. - **Pergunta 3**: Quais seriam as vantagens e desvantagens de utilizar um banco de dados NoSQL neste projeto? + - Acredito que usando nosql ficaria mais facil de escalar a aplicacao, porem a modelagem de dados seria mais complexa, e a performance poderia ser prejudicada. ### 3. Agilidade (Nível Básico) - **Pergunta 1**: Explique o conceito de metodologias ágeis e como elas impactam o desenvolvimento de software. + - Metodologias ageis sao metodologias de desenvolvimento de software que visam entregar valor ao cliente de forma rapida e continua, atraves de iteracoes curtas e feedbacks constantes. + - elas ajudam a itentificar problemas mais cedo, e a entregar valor ao cliente de uma maneira mais satisfatoria. - **Pergunta 2**: No desenvolvimento deste projeto, como você aplicaria princípios ágeis para garantir entregas contínuas e com qualidade? + - Posso realizar uma abordagem de desenvolvimento de software incremental, onde eu entregaria funcionalidades em pequenas partes, e com feedbacks constantes do cliente. Para garantir um pouco mais de qualidade, eu poderia utilizar a pratica de TDD. - **Pergunta 3**: Qual a importância da comunicação entre as equipes em um ambiente ágil? Dê exemplos de boas práticas. - + - Extremamente importante, pois ajuda tanto a alinhar expectativas, quanto a identificar problemas mais cedo. Daily seria um exemplo simples ### 4. DevOps (Nível Básico) - **Pergunta 1**: O que é DevOps e qual a sua importância para o ciclo de vida de uma aplicação? + - DevOps tem como objetivo facilitar processos de desenvolvimento e operacao, atraves de automacao, colaboracao e monitoramento. A importancia dele e garantir que a aplicacao seja entregue de forma rapida e com qualidade. - **Pergunta 2**: Descreva como você integraria práticas de DevOps no desenvolvimento desta aplicação de estacionamento. Inclua exemplos de CI/CD. + - Eu poderia utilizar uma ferramenta de CI/CD para automatizar o processo de deploy da aplicacao, e monitorar a aplicacao atraves de ferramentas de monitoramento. + - Nessa aplicacao por exemplo para facilitar o deploy eu poderia dockerizar a aplicacao, e utilizar o github para automatizar o processo de deploy. + - Daria para usar o ansible que ajudaria a organizar arquivos yaml segregando melhor os ambientes e criar comandos para facilitar geracao dessas profiles antes de dockerizar. - **Pergunta 3**: Cite as ferramentas que você usaria para automatizar o processo de deploy e monitoramento da aplicação. + - Docker, Github, Ansible, monitoramento eu poderia usar o prometheus e grafana. ## Submissão Crie um fork do teste para acompanharmos o seu desenvolvimento através dos seus commits. diff --git a/READMEAPPLICATION.md b/READMEAPPLICATION.md new file mode 100644 index 00000000..1294e423 --- /dev/null +++ b/READMEAPPLICATION.md @@ -0,0 +1,52 @@ +### Tecnologias usadas + +- [Spring Boot](https://spring.io/projects/spring-boot) +- [PostgreSQL](https://www.postgresql.org/) +- [Docker](https://www.docker.com/) +- [Spring Doc Open API](https://springdoc.org/) +- [Lombok](https://projectlombok.org/) +- [Maven](https://maven.apache.org/) +- [Java 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) +- [Mockito](https://site.mockito.org/) +- [JUnit](https://junit.org/junit5/) + + +### Chamadas + +- **Estabelecimento:** CRUD + - **GET** /estabelecimento/ + - **GET** /estabelecimento/{id} + - **POST** /estabelecimento/ + - **PUT** /estabelecimento/{id} + - **DELETE** /estabelecimento/{id} + +- **Veículos:** CRUD + - **GET** /veiculos/ + - **GET** /veiculos/{id} + - **POST** /veiculos/ + - **PUT** /veiculos/{id} + - **DELETE** /veiculos/{id} + +- **Controle de entrada e saída de veículos** + - **GET** /entrada-saida/vagas-disponiveis + - **POST** /entrada-saida/entrada + - **POST** /entrada-saida/saida + +### Documentacao via Swagger + +Adicionei um redirecinamento no endpoint padrao "/" que redireciona para o swagger diretamente + +- **Swagger:** http://localhost:8080/v3/api-docs + +### Como rodar o projeto + +1. Clone o projeto +2. Entre na pasta do projeto +3. Execute o comando `docker-compose up --build` para que ele suba uma instancia do postgres +4. Execute o comando `mvn clean install` para que ele baixe as dependencias e gere o jar +5. Execute a aplicacao na IDE que desejar ou execute o comando `java -jar target/{nome-do-jar}.jar` para rodar o jar gerado + +### Observações + +- Criei uma pasta `postman` caso deseje ultizar o postman para testar as chamadas +- Criei uma pasta `sampledata` caso queria alimentar o banco com alguns scripts python [sim eu sei que poderia ser escrito na migration do flyway xD] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..da85f1b3 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,12 @@ +version: '3' + +services: + postgres: + image: postgres:17.0-alpine3.19 + container_name: postgresdatasource + environment: + POSTGRES_USER: localuser + POSTGRES_PASSWORD: localpassword + POSTGRES_DB: fcamaradatabase + ports: + - 5432:5432 diff --git a/mvnw b/mvnw new file mode 100644 index 00000000..19529ddf --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 00000000..249bdf38 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..fadaa43e --- /dev/null +++ b/pom.xml @@ -0,0 +1,152 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + com.fcamarasantos + testebackendjava + 0.0.1-SNAPSHOT + testebackendjava + Criar uma API REST para gerenciar um estacionamento de carros e motos. + + + + + + + + + + + + + + + 17 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + org.flywaydb + flyway-core + + + + org.flywaydb + flyway-database-postgresql + + + + com.h2database + h2 + runtime + + + + org.postgresql + postgresql + runtime + + + org.projectlombok + lombok + true + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.6.0 + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.restdocs + spring-restdocs-mockmvc + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + + + + diff --git a/postman/Controle estabelecimento.postman_collection.json b/postman/Controle estabelecimento.postman_collection.json new file mode 100644 index 00000000..f3b6b091 --- /dev/null +++ b/postman/Controle estabelecimento.postman_collection.json @@ -0,0 +1,84 @@ +{ + "info": { + "_postman_id": "637726f3-fc24-4fcb-bbff-4146dcd6751e", + "name": "Controle estabelecimento", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "10119525" + }, + "item": [ + { + "name": "Entrada Veiculo Estabelecimento", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"idVeiculo\": 1,\r\n \"idEstabelecimento\": 10\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_api_url}}/entrada-saida/entrada-veiculo/", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "entrada-saida", + "entrada-veiculo", + "" + ] + } + }, + "response": [] + }, + { + "name": "Saida Veiculo Estabelecimento", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"idVeiculo\": 2,\r\n \"idEstabelecimento\": 10\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_api_url}}/estabelecimentos/1", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Vagas disponiveis", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_api_url}}/entrada-saida/vagas-disponiveis/10", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "entrada-saida", + "vagas-disponiveis", + "10" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/postman/Estabelecimento CRUD.postman_collection.json b/postman/Estabelecimento CRUD.postman_collection.json new file mode 100644 index 00000000..4b087755 --- /dev/null +++ b/postman/Estabelecimento CRUD.postman_collection.json @@ -0,0 +1,136 @@ +{ + "info": { + "_postman_id": "e9d85898-e887-4b78-b7ba-1334d45d96f7", + "name": "Estabelecimento CRUD", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "10119525" + }, + "item": [ + { + "name": "getAll Estabelecimento", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_api_url}}/estabelecimentos/", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "" + ] + } + }, + "response": [] + }, + { + "name": "getOne Estabelecimento", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_api_url}}/estabelecimentos/10", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "10" + ] + } + }, + "response": [] + }, + { + "name": "getOne Estabelecimento Copy", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_api_url}}/estabelecimentos/1", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "1" + ] + } + }, + "response": [] + }, + { + "name": "create Estabelecimento", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"id\": 5,\r\n \"nome\": \"Empresa X\",\r\n \"cnpj\": \"00000000001000\",\r\n \"telefone\": \"40028922\",\r\n \"endereco\": {\r\n \"numero\": \"707\",\r\n \"logradouro\": \"Rua J\",\r\n \"bairro\": \"Bairro J\",\r\n \"cidade\": \"Cidade J\",\r\n \"complemento\": \"Apto 10\",\r\n \"uf\": \"GO\",\r\n \"cep\": \"99999999\"\r\n },\r\n \"NumVagasMotos\": 3,\r\n \"NumVagasCarros\": 2\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_api_url}}/estabelecimentos/", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "" + ] + } + }, + "response": [] + }, + { + "name": "update Estabelecimento", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"nome\": \"Empresa Y\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_api_url}}/estabelecimentos/5", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "5" + ] + } + }, + "response": [] + }, + { + "name": "delete Estabelecimento", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{base_api_url}}/estabelecimentos/1", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "estabelecimentos", + "1" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/postman/Veiculo CRUD.postman_collection.json b/postman/Veiculo CRUD.postman_collection.json new file mode 100644 index 00000000..851a2731 --- /dev/null +++ b/postman/Veiculo CRUD.postman_collection.json @@ -0,0 +1,118 @@ +{ + "info": { + "_postman_id": "63e6049b-775e-4bc9-87ae-4091780cfc23", + "name": "Veiculo CRUD", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "10119525" + }, + "item": [ + { + "name": "getAll Veiculos", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_api_url}}/veiculos/", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "veiculos", + "" + ] + } + }, + "response": [] + }, + { + "name": "getOne Veiculo", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{base_api_url}}/veiculos/1", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "veiculos", + "1" + ] + } + }, + "response": [] + }, + { + "name": "create Veiculo", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"marca\": \"Yamaha\",\r\n \"modelo\": \"MT-03\",\r\n \"cor\": \"Preto\",\r\n \"placa\": \"QRS2T34\",\r\n \"tipo\": \"CARRO\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_api_url}}/veiculos/", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "veiculos", + "" + ] + } + }, + "response": [] + }, + { + "name": "update Veiculo", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"cor\": \"Azul\"\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_api_url}}/veiculos/1", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "veiculos", + "1" + ] + } + }, + "response": [] + }, + { + "name": "delete Veiculo", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{base_api_url}}/veiculos/10", + "host": [ + "{{base_api_url}}" + ], + "path": [ + "veiculos", + "10" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/sampledata/estabelecimentos.jsonl b/sampledata/estabelecimentos.jsonl new file mode 100644 index 00000000..ee410311 --- /dev/null +++ b/sampledata/estabelecimentos.jsonl @@ -0,0 +1,10 @@ +{"nome": "Empresa 1", "cnpj": "00000000000100", "endereco": {"numero": "123", "logradouro": "Rua A", "bairro": "Bairro A", "cidade": "Cidade A", "complemento": "Apto 1", "uf": "SP", "cep": "00000000"}, "telefone": "1100000000", "numVagasMotos": 10, "numVagasCarros": 20} +{"nome": "Empresa 2", "cnpj": "00000000000200", "endereco": {"numero": "456", "logradouro": "Rua B", "bairro": "Bairro B", "cidade": "Cidade B", "complemento": "Apto 2", "uf": "RJ", "cep": "11111111"}, "telefone": "2111111111", "numVagasMotos": 15, "numVagasCarros": 25} +{"nome": "Empresa 3", "cnpj": "00000000000300", "endereco": {"numero": "789", "logradouro": "Rua C", "bairro": "Bairro C", "cidade": "Cidade C", "complemento": "Apto 3", "uf": "MG", "cep": "22222222"}, "telefone": "3122222222", "numVagasMotos": 20, "numVagasCarros": 30} +{"nome": "Empresa 4", "cnpj": "00000000000400", "endereco": {"numero": "101", "logradouro": "Rua D", "bairro": "Bairro D", "cidade": "Cidade D", "complemento": "Apto 4", "uf": "RS", "cep": "33333333"}, "telefone": "5133333333", "numVagasMotos": 12, "numVagasCarros": 22} +{"nome": "Empresa 5", "cnpj": "00000000000500", "endereco": {"numero": "202", "logradouro": "Rua E", "bairro": "Bairro E", "cidade": "Cidade E", "complemento": "Apto 5", "uf": "PR", "cep": "44444444"}, "telefone": "4144444444", "numVagasMotos": 18, "numVagasCarros": 28} +{"nome": "Empresa 6", "cnpj": "00000000000600", "endereco": {"numero": "303", "logradouro": "Rua F", "bairro": "Bairro F", "cidade": "Cidade F", "complemento": "Apto 6", "uf": "SC", "cep": "55555555"}, "telefone": "4855555555", "numVagasMotos": 14, "numVagasCarros": 24} +{"nome": "Empresa 7", "cnpj": "00000000000700", "endereco": {"numero": "404", "logradouro": "Rua G", "bairro": "Bairro G", "cidade": "Cidade G", "complemento": "Apto 7", "uf": "BA", "cep": "66666666"}, "telefone": "7166666666", "numVagasMotos": 16, "numVagasCarros": 26} +{"nome": "Empresa 8", "cnpj": "00000000000800", "endereco": {"numero": "505", "logradouro": "Rua H", "bairro": "Bairro H", "cidade": "Cidade H", "complemento": "Apto 8", "uf": "PE", "cep": "77777777"}, "telefone": "8177777777", "numVagasMotos": 22, "numVagasCarros": 32} +{"nome": "Empresa 9", "cnpj": "00000000000900", "endereco": {"numero": "606", "logradouro": "Rua I", "bairro": "Bairro I", "cidade": "Cidade I", "complemento": "Apto 9", "uf": "CE", "cep": "88888888"}, "telefone": "8588888888", "numVagasMotos": 19, "numVagasCarros": 29} +{"nome": "Empresa 10", "cnpj": "00000000001000", "endereco": {"numero": "707", "logradouro": "Rua J", "bairro": "Bairro J", "cidade": "Cidade J", "complemento": "Apto 10", "uf": "GO", "cep": "99999999"}, "telefone": "6299999999", "numVagasMotos": 3, "numVagasCarros": 2} \ No newline at end of file diff --git a/sampledata/post_all_jsonl.py b/sampledata/post_all_jsonl.py new file mode 100644 index 00000000..ebac484e --- /dev/null +++ b/sampledata/post_all_jsonl.py @@ -0,0 +1,37 @@ +import os +import sys +import requests +import json + +if len(sys.argv) != 2: + print("Usage: python post_all_jsonl.py ") + sys.exit(1) + +def postAll(): + entities = [] + for filename in os.listdir(): + if filename.endswith(".jsonl"): + entities.append(filename.split(".")[0]) + + + print(f"Entities found: {entities}") + print("Posting data...") + + for entity in entities: + print(f"Posting data for {entity}...") + + with open(f"{entity}.jsonl", 'r') as file: + for line in file: + data = json.loads(line) + response = requests.post(f"{base_endpoint}/{entity}/", json=data) + if response.status_code >= 200 and response.status_code < 300: + print(f"Successfully posted: {data}") + else: + print(f"Failed to post: {data}, Status Code: {response.status_code}") + + print("Data posted.") + + +if __name__ == "__main__": + base_endpoint = sys.argv[1] + postAll() diff --git a/sampledata/post_local_data.py b/sampledata/post_local_data.py new file mode 100644 index 00000000..13def7f1 --- /dev/null +++ b/sampledata/post_local_data.py @@ -0,0 +1,19 @@ +import requests +import json +import sys + +if len(sys.argv) != 3: + print("Usage: python post_local_data.py ") + sys.exit(1) + +endpoint = sys.argv[1] +filepath = sys.argv[2] + +with open(filepath, 'r') as file: + for line in file: + data = json.loads(line) + response = requests.post(endpoint, json=data) + if response.status_code >= 200 and response.status_code < 300: + print(f"Successfully posted: {data}") + else: + print(f"Failed to post: {data}, Status Code: {response.status_code}") \ No newline at end of file diff --git a/sampledata/veiculos.jsonl b/sampledata/veiculos.jsonl new file mode 100644 index 00000000..0feeacf6 --- /dev/null +++ b/sampledata/veiculos.jsonl @@ -0,0 +1,40 @@ +{"marca": "Yamaha", "placa": "AAA-1111", "cor": "Red", "modelo": "YZF-R3", "tipo": "MOTO"} +{"marca": "Honda", "placa": "BBB-2222", "cor": "Blue", "modelo": "CBR500R", "tipo": "MOTO"} +{"marca": "Suzuki", "placa": "CCC-3333", "cor": "Black", "modelo": "GSX-R600", "tipo": "MOTO"} +{"marca": "Kawasaki", "placa": "DDD-4444", "cor": "Green", "modelo": "Ninja 400", "tipo": "MOTO"} +{"marca": "Ducati", "placa": "EEE-5555", "cor": "Red", "modelo": "Panigale V2", "tipo": "MOTO"} +{"marca": "BMW", "placa": "FFF-6666", "cor": "White", "modelo": "S1000RR", "tipo": "MOTO"} +{"marca": "Triumph", "placa": "GGG-7777", "cor": "Silver", "modelo": "Street Triple", "tipo": "MOTO"} +{"marca": "Harley-Davidson", "placa": "HHH-8888", "cor": "Black", "modelo": "Iron 883", "tipo": "MOTO"} +{"marca": "KTM", "placa": "III-9999", "cor": "Orange", "modelo": "Duke 390", "tipo": "MOTO"} +{"marca": "Aprilia", "placa": "JJJ-0000", "cor": "Red", "modelo": "RS 660", "tipo": "MOTO"} +{"marca": "MV Agusta", "placa": "KKK-1111", "cor": "Black", "modelo": "F3 800", "tipo": "MOTO"} +{"marca": "Royal Enfield", "placa": "LLL-2222", "cor": "Blue", "modelo": "Interceptor 650", "tipo": "MOTO"} +{"marca": "Indian", "placa": "MMM-3333", "cor": "Red", "modelo": "Scout", "tipo": "MOTO"} +{"marca": "Moto Guzzi", "placa": "NNN-4444", "cor": "Green", "modelo": "V7 III", "tipo": "MOTO"} +{"marca": "Husqvarna", "placa": "OOO-5555", "cor": "White", "modelo": "Vitpilen 401", "tipo": "MOTO"} +{"marca": "Benelli", "placa": "PPP-6666", "cor": "Silver", "modelo": "TNT 300", "tipo": "MOTO"} +{"marca": "Bajaj", "placa": "QQQ-7777", "cor": "Black", "modelo": "Pulsar NS200", "tipo": "MOTO"} +{"marca": "Hero", "placa": "RRR-8888", "cor": "Blue", "modelo": "Xtreme 200R", "tipo": "MOTO"} +{"marca": "TVS", "placa": "SSS-9999", "cor": "Red", "modelo": "Apache RTR 200", "tipo": "MOTO"} +{"marca": "CFMoto", "placa": "TTT-0000", "cor": "Orange", "modelo": "300NK", "tipo": "MOTO"} +{"marca": "Toyota", "placa": "ABC-1234", "cor": "Red", "modelo": "Corolla", "tipo": "CARRO"} +{"marca": "Honda", "placa": "DEF-5678", "cor": "Blue", "modelo": "Civic", "tipo": "CARRO"} +{"marca": "Ford", "placa": "GHI-9012", "cor": "Black", "modelo": "Focus", "tipo": "CARRO"} +{"marca": "Chevrolet", "placa": "JKL-3456", "cor": "White", "modelo": "Malibu", "tipo": "CARRO"} +{"marca": "Nissan", "placa": "MNO-7890", "cor": "Silver", "modelo": "Altima", "tipo": "CARRO"} +{"marca": "BMW", "placa": "PQR-1234", "cor": "Gray", "modelo": "X5", "tipo": "CARRO"} +{"marca": "Audi", "placa": "STU-5678", "cor": "Green", "modelo": "Q7", "tipo": "CARRO"} +{"marca": "Mercedes", "placa": "VWX-9012", "cor": "Yellow", "modelo": "GLC", "tipo": "CARRO"} +{"marca": "Volkswagen", "placa": "YZA-3456", "cor": "Blue", "modelo": "Tiguan", "tipo": "CARRO"} +{"marca": "Hyundai", "placa": "BCD-7890", "cor": "Red", "modelo": "Tucson", "tipo": "CARRO"} +{"marca": "Kia", "placa": "EFG-1234", "cor": "Black", "modelo": "Sportage", "tipo": "CARRO"} +{"marca": "Mazda", "placa": "HIJ-5678", "cor": "White", "modelo": "CX-5", "tipo": "CARRO"} +{"marca": "Subaru", "placa": "KLM-9012", "cor": "Silver", "modelo": "Forester", "tipo": "CARRO"} +{"marca": "Tesla", "placa": "NOP-3456", "cor": "Gray", "modelo": "Model 3", "tipo": "CARRO"} +{"marca": "Volvo", "placa": "QRS-7890", "cor": "Green", "modelo": "XC60", "tipo": "CARRO"} +{"marca": "Jaguar", "placa": "TUV-1234", "cor": "Yellow", "modelo": "F-Pace", "tipo": "CARRO"} +{"marca": "Land Rover", "placa": "WXY-5678", "cor": "Blue", "modelo": "Discovery", "tipo": "CARRO"} +{"marca": "Porsche", "placa": "ZAB-9012", "cor": "Red", "modelo": "Cayenne", "tipo": "CARRO"} +{"marca": "Ferrari", "placa": "CDE-3456", "cor": "Black", "modelo": "488", "tipo": "CARRO"} +{"marca": "Lamborghini", "placa": "FGH-7890", "cor": "White", "modelo": "Huracan", "tipo": "CARRO"} \ No newline at end of file diff --git a/src/main/java/com/fcamarasantos/testebackendjava/TestebackendjavaApplication.java b/src/main/java/com/fcamarasantos/testebackendjava/TestebackendjavaApplication.java new file mode 100644 index 00000000..fd4a56e3 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/TestebackendjavaApplication.java @@ -0,0 +1,13 @@ +package com.fcamarasantos.testebackendjava; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class TestebackendjavaApplication { + + public static void main(String[] args) { + SpringApplication.run(TestebackendjavaApplication.class, args); + } + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/controller/EntradaSaidaController.java b/src/main/java/com/fcamarasantos/testebackendjava/controller/EntradaSaidaController.java new file mode 100644 index 00000000..f90a8264 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/controller/EntradaSaidaController.java @@ -0,0 +1,41 @@ +package com.fcamarasantos.testebackendjava.controller; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.VagasDisponiveisDTO; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.RegistroEntradaSaidaService; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.SaidaVeiculoDTO; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping(value = "entrada-saida/") +public class EntradaSaidaController { + + @Autowired + private RegistroEntradaSaidaService registroEntradaSaidaService; + + @PostMapping("entrada-veiculo/") + @Transactional + public ResponseEntity entradaVeiculo(@Valid @RequestBody EntradaVeiculoDTO entradaVeiculoDTO) { + registroEntradaSaidaService.entradaVeiculo(entradaVeiculoDTO); + return ResponseEntity.ok().build(); + } + + @PostMapping("saida-veiculo/") + @Transactional + public ResponseEntity saidaVeiculo(@Valid @RequestBody SaidaVeiculoDTO saidaVeiculoDTO) { + registroEntradaSaidaService.saidaVeiculo(saidaVeiculoDTO); + return ResponseEntity.ok().build(); + } + + @GetMapping("vagas-disponiveis/{idEstabelecimento}") + public ResponseEntity vagasDisponiveis(@Valid @PathVariable Long idEstabelecimento) { + return ResponseEntity.ok( + new VagasDisponiveisDTO(registroEntradaSaidaService.vagasDisponiveis(idEstabelecimento)) + ); + } + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/controller/EstabelecimentoController.java b/src/main/java/com/fcamarasantos/testebackendjava/controller/EstabelecimentoController.java new file mode 100644 index 00000000..6718a423 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/controller/EstabelecimentoController.java @@ -0,0 +1,78 @@ +package com.fcamarasantos.testebackendjava.controller; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.Estabelecimento; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.EstabelecimentoRepository; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.VagasDisponiveis; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.CreateEstabelecimentoDTO; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.EstabelecimentoDetailsDto; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.UpdateEstabelecimentoDTO; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.VagasDisponiveisDTO; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; + +@RestController +@RequestMapping("estabelecimentos/") +public class EstabelecimentoController { + + @Autowired + private EstabelecimentoRepository estabelecimentoRepository; + + @GetMapping + public ResponseEntity> findAllEstabelecimentos(Pageable pageable) { + return ResponseEntity.ok( + estabelecimentoRepository + .findAll(pageable) + .map(EstabelecimentoDetailsDto::new) + ); + } + + @PostMapping + @Transactional + public ResponseEntity createEstabelecimento( + @RequestBody @Valid CreateEstabelecimentoDTO createEstabelecimentoDTO, + UriComponentsBuilder uriBuilder + ) { + var estabelecimento = new Estabelecimento(createEstabelecimentoDTO); + var criado = estabelecimentoRepository.save(estabelecimento); + + var uri = uriBuilder.path("/estabelecimentos/{id}") + .buildAndExpand(criado.getId()).toUri(); + + return ResponseEntity. + created(uri) + .body(new EstabelecimentoDetailsDto(criado)); + } + + @PutMapping("/{id}") + @Transactional + public ResponseEntity updateEstabelecimento( + @PathVariable Long id, + @Valid @RequestBody UpdateEstabelecimentoDTO updateEstabelecimentoDTO + ) { + var estabelecimento = estabelecimentoRepository.getReferenceById(id); + estabelecimento.updateEstabelecimento(updateEstabelecimentoDTO); + + return ResponseEntity.ok(new EstabelecimentoDetailsDto(estabelecimento)); + } + + @GetMapping("/{id}") + public ResponseEntity getEstabelecimento(@PathVariable Long id) { + var estabelecimento = estabelecimentoRepository.getReferenceById(id); + return ResponseEntity.ok(new EstabelecimentoDetailsDto(estabelecimento)); + } + + @DeleteMapping("/{id}") + @Transactional + public ResponseEntity deleteEstabelecimento(@PathVariable Long id) { + var estabelecimento = estabelecimentoRepository.getReferenceById(id); + estabelecimentoRepository.delete(estabelecimento); + + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/controller/HomeController.java b/src/main/java/com/fcamarasantos/testebackendjava/controller/HomeController.java new file mode 100644 index 00000000..16f43025 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/controller/HomeController.java @@ -0,0 +1,21 @@ +package com.fcamarasantos.testebackendjava.controller; + + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +@RestController +@RequestMapping("/") +public class HomeController { + + @Value("${springdoc.swagger-ui.path}") + private String documentationUrl; + + @GetMapping + public ModelAndView redirectToDocumentation() { + return new ModelAndView("redirect:" + documentationUrl); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/controller/VeiculoController.java b/src/main/java/com/fcamarasantos/testebackendjava/controller/VeiculoController.java new file mode 100644 index 00000000..fbc98cc2 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/controller/VeiculoController.java @@ -0,0 +1,76 @@ +package com.fcamarasantos.testebackendjava.controller; + +import com.fcamarasantos.testebackendjava.domain.veiculo.Veiculo; +import com.fcamarasantos.testebackendjava.domain.veiculo.VeiculoRepository; +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.CreateVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.UpdateVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.VeiculoDetailsDTO; +import jakarta.validation.Valid; +import jakarta.transaction.Transactional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.beans.factory.annotation.Autowired; + +@RestController +@RequestMapping("veiculos/") +public class VeiculoController { + + @Autowired + private VeiculoRepository veiculoRepository; + + @GetMapping + public ResponseEntity> findAllVeiculos(Pageable pageable) { + return ResponseEntity.ok( + veiculoRepository + .findAll(pageable) + .map(VeiculoDetailsDTO::new) + ); + } + + @PostMapping + @Transactional + public ResponseEntity createVeiculo( + @RequestBody @Valid CreateVeiculoDTO createVeiculoDTO, + UriComponentsBuilder uriBuilder + ) { + var veiculo = new Veiculo(createVeiculoDTO); + var criado = veiculoRepository.save(veiculo); + + var uri = uriBuilder.path("/veiculos/{id}") + .buildAndExpand(criado.getId()).toUri(); + + return ResponseEntity. + created(uri) + .body(new VeiculoDetailsDTO(criado)); + } + + @PutMapping("/{id}") + @Transactional + public ResponseEntity updateVeiculo( + @PathVariable Long id, + @Valid @RequestBody UpdateVeiculoDTO updateVeiculoDTO + ) { + var veiculo = veiculoRepository.getReferenceById(id); + veiculo.updateVeiculo(updateVeiculoDTO); + + return ResponseEntity.ok(new VeiculoDetailsDTO(veiculo)); + } + + @GetMapping("/{id}") + public ResponseEntity getVeiculo(@PathVariable Long id) { + var veiculo = veiculoRepository.getReferenceById(id); + return ResponseEntity.ok(new VeiculoDetailsDTO(veiculo)); + } + + @DeleteMapping("/{id}") + @Transactional + public ResponseEntity deleteVeiculo(@PathVariable Long id) { + var veiculo = veiculoRepository.getReferenceById(id); + veiculoRepository.delete(veiculo); + + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/Endereco.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/Endereco.java new file mode 100644 index 00000000..50a79166 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/Endereco.java @@ -0,0 +1,34 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.EnderecoDTO; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class Endereco { + + public String numero; + public String logradouro; + public String bairro; + public String cidade; + public String complemento; + public String uf; + public String cep; + + public Endereco(EnderecoDTO endereco) { + this.numero = endereco.numero(); + this.logradouro = endereco.logradouro(); + this.bairro = endereco.bairro(); + this.cidade = endereco.cidade(); + this.complemento = endereco.complemento(); + this.uf = endereco.uf(); + this.cep = endereco.cep(); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/Estabelecimento.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/Estabelecimento.java new file mode 100644 index 00000000..1fb5934c --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/Estabelecimento.java @@ -0,0 +1,74 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.CreateEstabelecimentoDTO; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.dto.UpdateEstabelecimentoDTO; +import jakarta.persistence.*; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Table(name = "estabelecimentos") +@Entity(name = "Estabelecimento") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Estabelecimento { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + public String nome; + + public String telefone; + + public String cnpj; + + @Embedded + public Endereco endereco; + + @Column(name = "num_vagas_motos") + public Integer numVagasMotos; + + @Column(name = "num_vagas_carros") + public Integer numVagasCarros; + + public Estabelecimento(CreateEstabelecimentoDTO createEstabelecimentoDTO) { + this.nome = createEstabelecimentoDTO.nome(); + this.telefone = createEstabelecimentoDTO.telefone(); + this.cnpj = createEstabelecimentoDTO.cnpj(); + this.endereco = new Endereco(createEstabelecimentoDTO.endereco()); + this.numVagasMotos = createEstabelecimentoDTO.numVagasMotos(); + this.numVagasCarros = createEstabelecimentoDTO.numVagasCarros(); + + } + + public void updateEstabelecimento(UpdateEstabelecimentoDTO updateEstabelecimentoDTO) { + if (updateEstabelecimentoDTO.nome() != null) { + this.nome = updateEstabelecimentoDTO.nome(); + } + + if (updateEstabelecimentoDTO.telefone() != null) { + this.telefone = updateEstabelecimentoDTO.telefone(); + } + + if (updateEstabelecimentoDTO.cnpj() != null) { + this.cnpj = updateEstabelecimentoDTO.cnpj(); + } + + if (updateEstabelecimentoDTO.endereco() != null) { + this.endereco = new Endereco(updateEstabelecimentoDTO.endereco()); + } + + if (updateEstabelecimentoDTO.numVagasMotos() != null) { + this.numVagasMotos = updateEstabelecimentoDTO.numVagasMotos(); + } + + if (updateEstabelecimentoDTO.numVagasCarros() != null) { + this.numVagasCarros = updateEstabelecimentoDTO.numVagasCarros(); + } + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/EstabelecimentoRepository.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/EstabelecimentoRepository.java new file mode 100644 index 00000000..c9b2f11a --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/EstabelecimentoRepository.java @@ -0,0 +1,9 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.RegistroEntradaSaida; +import jakarta.validation.Valid; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface EstabelecimentoRepository extends JpaRepository { + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/VagasDisponiveis.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/VagasDisponiveis.java new file mode 100644 index 00000000..f91a6d16 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/VagasDisponiveis.java @@ -0,0 +1,20 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento; + +import jakarta.persistence.Entity; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@Getter +@Setter +public class VagasDisponiveis { + private int numVagasMotos; + private int numVagasCarros; + + public VagasDisponiveis(int numVagasMotos, int numVagasCarros) { + this.numVagasMotos = numVagasMotos; + this.numVagasCarros = numVagasCarros; + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/CreateEstabelecimentoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/CreateEstabelecimentoDTO.java new file mode 100644 index 00000000..b146565d --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/CreateEstabelecimentoDTO.java @@ -0,0 +1,27 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.PositiveOrZero; + +public record CreateEstabelecimentoDTO( + @NotBlank + String nome, + + @NotBlank @Pattern(regexp = "^\\d{14}$") + String cnpj, + + @NotBlank @Pattern(regexp = "^\\d{8,20}$") + String telefone, + + @Valid + EnderecoDTO endereco, + + @PositiveOrZero + Integer numVagasMotos, + + @PositiveOrZero + Integer numVagasCarros +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/EnderecoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/EnderecoDTO.java new file mode 100644 index 00000000..f482c274 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/EnderecoDTO.java @@ -0,0 +1,41 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento.dto; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.Endereco; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public record EnderecoDTO( + + @NotBlank + String numero, + + @NotBlank + String logradouro, + + @NotBlank + String bairro, + + @NotBlank + String cidade, + + String complemento, + + @Pattern(regexp = "^[A-Z]{2}$") + String uf, + + @Pattern(regexp = "^\\d{8}$") + String cep +) { + + public EnderecoDTO(Endereco endereco) { + this( + endereco.getNumero(), + endereco.getLogradouro(), + endereco.getBairro(), + endereco.getCidade(), + endereco.getComplemento(), + endereco.getUf(), + endereco.getCep() + ); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/EstabelecimentoDetailsDto.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/EstabelecimentoDetailsDto.java new file mode 100644 index 00000000..877a3d10 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/EstabelecimentoDetailsDto.java @@ -0,0 +1,26 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento.dto; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.Endereco; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.Estabelecimento; + +public record EstabelecimentoDetailsDto( + Long id, + String nome, + String cnpj, + String telefone, + EnderecoDTO endereco, + Integer NumVagasMotos, + Integer NumVagasCarros +) { + public EstabelecimentoDetailsDto(Estabelecimento estabelecimento) { + this( + estabelecimento.getId(), + estabelecimento.getNome(), + estabelecimento.getCnpj(), + estabelecimento.getTelefone(), + new EnderecoDTO(estabelecimento.getEndereco()), + estabelecimento.getNumVagasMotos(), + estabelecimento.getNumVagasCarros() + ); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/UpdateEstabelecimentoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/UpdateEstabelecimentoDTO.java new file mode 100644 index 00000000..31443f5d --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/UpdateEstabelecimentoDTO.java @@ -0,0 +1,26 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.PositiveOrZero; + +public record UpdateEstabelecimentoDTO( + String nome, + + @Pattern(regexp = "^\\d{14}$") + String cnpj, + + @NotBlank @Pattern(regexp = "^\\d{8,20}$") + String telefone, + + @Valid + EnderecoDTO endereco, + + @PositiveOrZero + Integer numVagasMotos, + + @PositiveOrZero + Integer numVagasCarros +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/VagasDisponiveisDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/VagasDisponiveisDTO.java new file mode 100644 index 00000000..20180f58 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/estabelecimento/dto/VagasDisponiveisDTO.java @@ -0,0 +1,14 @@ +package com.fcamarasantos.testebackendjava.domain.estabelecimento.dto; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.VagasDisponiveis; + +public record VagasDisponiveisDTO( + int numVagasMotos, + int numVagasCarros +) { + public VagasDisponiveisDTO( + VagasDisponiveis vagasDisponiveis + ) { + this(vagasDisponiveis.getNumVagasMotos(), vagasDisponiveis.getNumVagasCarros()); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaida.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaida.java new file mode 100644 index 00000000..41c7618a --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaida.java @@ -0,0 +1,39 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.Estabelecimento; +import com.fcamarasantos.testebackendjava.domain.veiculo.Veiculo; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Table(name = "registros_entrada_saida") +@Entity(name = "RegistroEntradaSaida") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RegistroEntradaSaida { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "estabelecimento_id") + public Estabelecimento estabelecimento; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "veiculo_id") + public Veiculo veiculo; + + @Column(name = "data_entrada") + public LocalDateTime dataEntrada; + + @Column(name = "data_saida") + public LocalDateTime dataSaida; + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaidaRepository.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaidaRepository.java new file mode 100644 index 00000000..f37d95ef --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaidaRepository.java @@ -0,0 +1,33 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.Estabelecimento; +import com.fcamarasantos.testebackendjava.domain.veiculo.TipoVeiculo; +import com.fcamarasantos.testebackendjava.domain.veiculo.Veiculo; +import jakarta.validation.constraints.NotNull; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface RegistroEntradaSaidaRepository extends JpaRepository { + + @Query(""" + SELECT r FROM + RegistroEntradaSaida r + WHERE + r.estabelecimento.id = :idEstabelecimento AND + r.veiculo.id = :idVeiculo AND + r.dataSaida is NULL + """) + RegistroEntradaSaida buscarRegistroRelacionado(@NotNull Long idEstabelecimento, @NotNull Long idVeiculo); + + @Query(""" + select count(r) from + RegistroEntradaSaida r + where + r.estabelecimento.id = :idEstabelecimento and + r.veiculo.tipo = :tipoVeiculo and + r.dataSaida IS NULL + """) + Integer getVagasDisponiveis(@NotNull Long idEstabelecimento, TipoVeiculo tipoVeiculo); + + Boolean existsByVeiculoIdAndDataSaidaIsNull(@NotNull Long idVeiculo); +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaidaService.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaidaService.java new file mode 100644 index 00000000..5020ffe5 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/RegistroEntradaSaidaService.java @@ -0,0 +1,103 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.VagasDisponiveis; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.SaidaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.estabelecimento.EstabelecimentoRepository; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.entradaVeiculos.EntradaVeiculosValidator; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.saidaVeiculos.SaidaVeiculosValidator; +import com.fcamarasantos.testebackendjava.domain.veiculo.TipoVeiculo; +import com.fcamarasantos.testebackendjava.domain.veiculo.VeiculoRepository; +import com.fcamarasantos.testebackendjava.infra.exceptions.BusinessValidationException; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +public class RegistroEntradaSaidaService { + + @Autowired + private RegistroEntradaSaidaRepository controleEstabelecimentoRepository; + + @Autowired + private EstabelecimentoRepository estabelecimentoRepository; + + @Autowired + private VeiculoRepository veiculoRepository; + + @Autowired + private List entradaVeiculosValidators; + + @Autowired + private List saidaVeiculosValidators; + + public void entradaVeiculo(EntradaVeiculoDTO entradaVeiculoDTO) { + var idEstabelecimento = entradaVeiculoDTO.idEstabelecimento(); + var idVeiculo = entradaVeiculoDTO.idVeiculo(); + + entradaVeiculosValidators.forEach(validator -> validator.validate(entradaVeiculoDTO)); + + var estabelecimento = estabelecimentoRepository.getReferenceById(idEstabelecimento); + var veiculo = veiculoRepository.getReferenceById(idVeiculo); + + var controleEstabelecimento = new RegistroEntradaSaida( + null, + estabelecimento, + veiculo, + LocalDateTime.now(), + null + ); + + controleEstabelecimentoRepository.save(controleEstabelecimento); + + System.out.printf("Entrada de veículo %s no estabelecimento %s em %s \n", idVeiculo, idEstabelecimento, LocalDateTime.now()); + } + + public void saidaVeiculo(SaidaVeiculoDTO saidaVeiculoDTO) { + var idEstabelecimento = saidaVeiculoDTO.idEstabelecimento(); + var idVeiculo = saidaVeiculoDTO.idVeiculo(); + + saidaVeiculosValidators.forEach(validator -> validator.validate(saidaVeiculoDTO)); + + var controleEstabelecimento = getControleEstabelecimento( + saidaVeiculoDTO.idEstabelecimento(), + saidaVeiculoDTO.idVeiculo() + ); + + if (controleEstabelecimento == null) { + throw new BusinessValidationException("Veiculo não encontrado em nenhum estabelecimento"); + } + + controleEstabelecimento.setDataSaida(LocalDateTime.now()); + + System.out.printf("Saída de veículo %s do estabelecimento %s em %s \n", idVeiculo, idEstabelecimento, LocalDateTime.now()); + } + + private RegistroEntradaSaida getControleEstabelecimento(Long idEstabelecimento, Long idVeiculo) { + var controleEstabelecimento = controleEstabelecimentoRepository + .buscarRegistroRelacionado( + idEstabelecimento, idVeiculo + ); + + return controleEstabelecimento; + } + + public VagasDisponiveis vagasDisponiveis(@Valid Long idEstabelecimento) { + var estabelecimento = estabelecimentoRepository + .getReferenceById(idEstabelecimento); + + var vagasCarrosPreenchidas = controleEstabelecimentoRepository + .getVagasDisponiveis(idEstabelecimento, TipoVeiculo.CARRO); + + var vagasMotosPreenchidas = controleEstabelecimentoRepository + .getVagasDisponiveis(idEstabelecimento, TipoVeiculo.MOTO); + + return new VagasDisponiveis( + estabelecimento.getNumVagasMotos() - vagasMotosPreenchidas, + estabelecimento.getNumVagasCarros() - vagasCarrosPreenchidas + ); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/EntradaVeiculoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/EntradaVeiculoDTO.java new file mode 100644 index 00000000..409f9178 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/EntradaVeiculoDTO.java @@ -0,0 +1,12 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto; + +import jakarta.validation.constraints.NotNull; + +public record EntradaVeiculoDTO( + @NotNull + Long idVeiculo, + + @NotNull + Long idEstabelecimento +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/EntradaVeiculoDetailsDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/EntradaVeiculoDetailsDTO.java new file mode 100644 index 00000000..3946ccc0 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/EntradaVeiculoDetailsDTO.java @@ -0,0 +1,12 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto; + +import java.time.LocalDateTime; + +public record EntradaVeiculoDetailsDTO( + Long idVeiculo, + + Long idEstabelecimento, + + LocalDateTime dataEntrada +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/SaidaVeiculoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/SaidaVeiculoDTO.java new file mode 100644 index 00000000..925dfe1e --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/SaidaVeiculoDTO.java @@ -0,0 +1,12 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto; + +import jakarta.validation.constraints.NotNull; + +public record SaidaVeiculoDTO( + @NotNull + Long idVeiculo, + + @NotNull + Long idEstabelecimento +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/SaidaVeiculoDetailsDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/SaidaVeiculoDetailsDTO.java new file mode 100644 index 00000000..0b998d2c --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/dto/SaidaVeiculoDetailsDTO.java @@ -0,0 +1,14 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto; + +import java.time.LocalDateTime; + +public record SaidaVeiculoDetailsDTO( + Long idVeiculo, + + Long idEstabelecimento, + + LocalDateTime dataEntrada, + + LocalDateTime dataSaida +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/EntradaVeiculosValidator.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/EntradaVeiculosValidator.java new file mode 100644 index 00000000..3be2c291 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/EntradaVeiculosValidator.java @@ -0,0 +1,9 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.entradaVeiculos; + +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; + +public interface EntradaVeiculosValidator { + + void validate(EntradaVeiculoDTO data); + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VagasDisponiveisValidator.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VagasDisponiveisValidator.java new file mode 100644 index 00000000..c6c90130 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VagasDisponiveisValidator.java @@ -0,0 +1,45 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.entradaVeiculos; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.EstabelecimentoRepository; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.RegistroEntradaSaidaRepository; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.VeiculoRepository; +import com.fcamarasantos.testebackendjava.infra.exceptions.BusinessValidationException; +import jakarta.validation.ValidationException; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class VagasDisponiveisValidator implements EntradaVeiculosValidator { + + @Autowired + private VeiculoRepository veiculoRepository; + + @Autowired + private EstabelecimentoRepository estabelecimentoRepository; + + @Autowired + private RegistroEntradaSaidaRepository registroEntradaSaidaRepository; + + @SneakyThrows + @Override + public void validate(EntradaVeiculoDTO data) { + var veiculo = veiculoRepository + .getReferenceById(data.idVeiculo()); + + var estabelecimento = estabelecimentoRepository + .getReferenceById(data.idEstabelecimento()); + + var numVagasPreenchidas = registroEntradaSaidaRepository + .getVagasDisponiveis(data.idEstabelecimento(), veiculo.getTipo()); + + var numVagas = (veiculo.getTipo().name().equals("CARRO")) ? + estabelecimento.getNumVagasCarros() + : estabelecimento.getNumVagasMotos(); + + if (numVagas - numVagasPreenchidas == 0) { + throw new BusinessValidationException("Não há vagas disponíveis para veículos do tipo " + veiculo.getTipo().name()); + } + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VerificaEntidadesValidator.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VerificaEntidadesValidator.java new file mode 100644 index 00000000..069dbdb4 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VerificaEntidadesValidator.java @@ -0,0 +1,38 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.entradaVeiculos; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.EstabelecimentoRepository; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.VeiculoRepository; +import com.fcamarasantos.testebackendjava.infra.exceptions.BusinessValidationException; +import jakarta.validation.ValidationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("VerificaEntidadesEntradaVeiculosValidator") +public class VerificaEntidadesValidator implements EntradaVeiculosValidator { + + @Autowired + private VeiculoRepository veiculoRepository; + + @Autowired + private EstabelecimentoRepository estabelecimentoRepository; + + @Override + public void validate(EntradaVeiculoDTO data) { + var veiculo = veiculoRepository + .findById(data.idVeiculo()); + + var estabelecimento = estabelecimentoRepository + .findById(data.idEstabelecimento()); + + + if (veiculo.isEmpty()) { + throw new BusinessValidationException("Veículo não encontrado"); + } + + if (estabelecimento.isEmpty()) { + throw new BusinessValidationException("Estabelecimento não encontrado"); + } + + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VerificaJaEstacionadoValidator.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VerificaJaEstacionadoValidator.java new file mode 100644 index 00000000..e840fdd8 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/entradaVeiculos/VerificaJaEstacionadoValidator.java @@ -0,0 +1,25 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.entradaVeiculos; + +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.RegistroEntradaSaidaRepository; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.infra.exceptions.BusinessValidationException; +import jakarta.validation.ValidationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class VerificaJaEstacionadoValidator implements EntradaVeiculosValidator { + + @Autowired + private RegistroEntradaSaidaRepository registroEntradaSaidaRepository; + + @Override + public void validate(EntradaVeiculoDTO data) { + var estaEstacionado = registroEntradaSaidaRepository + .existsByVeiculoIdAndDataSaidaIsNull(data.idVeiculo()); + + if (estaEstacionado) { + throw new BusinessValidationException("Veículo já estacionado, o mesmo deve ser retirado antes de estacionar novamente em um estabelecimento"); + } + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/saidaVeiculos/SaidaVeiculosValidator.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/saidaVeiculos/SaidaVeiculosValidator.java new file mode 100644 index 00000000..289cd627 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/saidaVeiculos/SaidaVeiculosValidator.java @@ -0,0 +1,10 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.saidaVeiculos; + +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.SaidaVeiculoDTO; + +public interface SaidaVeiculosValidator { + + void validate(SaidaVeiculoDTO data) ; + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/saidaVeiculos/VerificaEntidadesValidator.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/saidaVeiculos/VerificaEntidadesValidator.java new file mode 100644 index 00000000..45e6e7d2 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/registroEntradaSaida/validators/saidaVeiculos/VerificaEntidadesValidator.java @@ -0,0 +1,39 @@ +package com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.validators.saidaVeiculos; + +import com.fcamarasantos.testebackendjava.domain.estabelecimento.EstabelecimentoRepository; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.EntradaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.registroEntradaSaida.dto.SaidaVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.VeiculoRepository; +import com.fcamarasantos.testebackendjava.infra.exceptions.BusinessValidationException; +import jakarta.validation.ValidationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("VerificaEntidadesSaidaVeiculosValidator") +public class VerificaEntidadesValidator implements SaidaVeiculosValidator { + + @Autowired + private VeiculoRepository veiculoRepository; + + @Autowired + private EstabelecimentoRepository estabelecimentoRepository; + + @Override + public void validate(SaidaVeiculoDTO data) { + var veiculo = veiculoRepository + .findById(data.idVeiculo()); + + var estabelecimento = estabelecimentoRepository + .findById(data.idEstabelecimento()); + + + if (veiculo.isEmpty()) { + throw new BusinessValidationException("Veículo não encontrado"); + } + + if (estabelecimento.isEmpty()) { + throw new BusinessValidationException("Estabelecimento não encontrado"); + } + + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/TipoVeiculo.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/TipoVeiculo.java new file mode 100644 index 00000000..b984cd4c --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/TipoVeiculo.java @@ -0,0 +1,6 @@ +package com.fcamarasantos.testebackendjava.domain.veiculo; + +public enum TipoVeiculo { + CARRO, + MOTO +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/Veiculo.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/Veiculo.java new file mode 100644 index 00000000..3b719842 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/Veiculo.java @@ -0,0 +1,54 @@ +package com.fcamarasantos.testebackendjava.domain.veiculo; + +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.CreateVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.UpdateVeiculoDTO; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Table(name = "veiculos") +@Entity(name = "Veiculo") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class Veiculo { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String marca; + private String modelo; + private String placa; + private String cor; + + @Enumerated(EnumType.STRING) + private TipoVeiculo tipo; + + public Veiculo(CreateVeiculoDTO createVeiculoDTO) { + this.modelo = createVeiculoDTO.modelo(); + this.marca = createVeiculoDTO.marca(); + this.placa = createVeiculoDTO.placa(); + this.tipo = createVeiculoDTO.tipo(); + this.cor = createVeiculoDTO.cor(); + } + + + public void updateVeiculo(UpdateVeiculoDTO updateVeiculoDTO) { + if (updateVeiculoDTO.marca() != null) { + this.marca = updateVeiculoDTO.marca(); + } + if (updateVeiculoDTO.modelo() != null) { + this.modelo = updateVeiculoDTO.modelo(); + } + if (updateVeiculoDTO.placa() != null) { + this.placa = updateVeiculoDTO.placa(); + } + if (updateVeiculoDTO.cor() != null) { + this.cor = updateVeiculoDTO.cor(); + } + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/VeiculoRepository.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/VeiculoRepository.java new file mode 100644 index 00000000..31070f51 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/VeiculoRepository.java @@ -0,0 +1,7 @@ +package com.fcamarasantos.testebackendjava.domain.veiculo; + +import jakarta.validation.constraints.NotNull; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VeiculoRepository extends JpaRepository { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/CreateVeiculoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/CreateVeiculoDTO.java new file mode 100644 index 00000000..fcdcd7d4 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/CreateVeiculoDTO.java @@ -0,0 +1,23 @@ +package com.fcamarasantos.testebackendjava.domain.veiculo.dto; + +import com.fcamarasantos.testebackendjava.domain.veiculo.TipoVeiculo; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record CreateVeiculoDTO( + @NotBlank + String marca, + + @NotBlank + String modelo, + + @NotBlank + String placa, + + @NotBlank + String cor, + + @NotNull + TipoVeiculo tipo +) { +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/UpdateVeiculoDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/UpdateVeiculoDTO.java new file mode 100644 index 00000000..85e62c30 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/UpdateVeiculoDTO.java @@ -0,0 +1,13 @@ +package com.fcamarasantos.testebackendjava.domain.veiculo.dto; + +import com.fcamarasantos.testebackendjava.domain.veiculo.TipoVeiculo; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record UpdateVeiculoDTO( + String marca, + String modelo, + String placa, + String cor +) { +} \ No newline at end of file diff --git a/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/VeiculoDetailsDTO.java b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/VeiculoDetailsDTO.java new file mode 100644 index 00000000..433833d9 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/domain/veiculo/dto/VeiculoDetailsDTO.java @@ -0,0 +1,25 @@ +package com.fcamarasantos.testebackendjava.domain.veiculo.dto; + +import com.fcamarasantos.testebackendjava.domain.veiculo.TipoVeiculo; +import com.fcamarasantos.testebackendjava.domain.veiculo.Veiculo; + +public record VeiculoDetailsDTO( + Long id, + String marca, + String modelo, + String placa, + String cor, + TipoVeiculo tipo +) { + + public VeiculoDetailsDTO(Veiculo veiculo) { + this( + veiculo.getId(), + veiculo.getMarca(), + veiculo.getModelo(), + veiculo.getPlaca(), + veiculo.getCor(), + veiculo.getTipo() + ); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/infra/documentation/SpringDocConfigurations.java b/src/main/java/com/fcamarasantos/testebackendjava/infra/documentation/SpringDocConfigurations.java new file mode 100644 index 00000000..f1f1da2b --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/infra/documentation/SpringDocConfigurations.java @@ -0,0 +1,24 @@ +package com.fcamarasantos.testebackendjava.infra.documentation; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SpringDocConfigurations { + + @Bean + public OpenAPI customOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("FCamara API") + .description("Teste para vaga de Desenvolvedor Back-end\n" + + "Criar uma API REST para gerenciar um estacionamento de carros e motos.") + .contact(new Contact() + .name("Time backend") + .email("devlinomota@gmail.com"))); + } + +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/infra/exceptions/BusinessValidationException.java b/src/main/java/com/fcamarasantos/testebackendjava/infra/exceptions/BusinessValidationException.java new file mode 100644 index 00000000..57b96699 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/infra/exceptions/BusinessValidationException.java @@ -0,0 +1,7 @@ +package com.fcamarasantos.testebackendjava.infra.exceptions; + +public class BusinessValidationException extends RuntimeException { + public BusinessValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/infra/exceptions/RestErrorAdvice.java b/src/main/java/com/fcamarasantos/testebackendjava/infra/exceptions/RestErrorAdvice.java new file mode 100644 index 00000000..ecf5a48d --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/infra/exceptions/RestErrorAdvice.java @@ -0,0 +1,69 @@ +package com.fcamarasantos.testebackendjava.infra.exceptions; + +import io.swagger.v3.oas.annotations.Hidden; +import jakarta.persistence.EntityNotFoundException; +import jakarta.validation.ValidationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Hidden +@RestControllerAdvice +public class RestErrorAdvice { + + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity entityNotFound() { + return ResponseEntity.notFound().build(); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity methodArgumentNotValidException(MethodArgumentNotValidException exception) { + var validationErrorsList = exception + .getFieldErrors() + .stream() + .map(ValidationErrorMessage::new) + .toList(); + + return ResponseEntity + .badRequest() + .body(validationErrorsList); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + private ResponseEntity HttpMessageNotReadableException(HttpMessageNotReadableException exception) { + return ResponseEntity + .badRequest() + .body(new NotReadableErrorMessage(exception)); + } + + @ExceptionHandler(BusinessValidationException.class) + public ResponseEntity validationException(BusinessValidationException exception) { + return ResponseEntity + .badRequest() + .body(new ErrorMessage(exception.getMessage())); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity serverInternalError(Exception ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Erro: " + ex.getLocalizedMessage()); + } + + private record ValidationErrorMessage(String field, String message) { + ValidationErrorMessage(FieldError fieldError) { + this(fieldError.getField(), fieldError.getDefaultMessage()); + } + } + + private record NotReadableErrorMessage(String message) { + NotReadableErrorMessage(HttpMessageNotReadableException exception) { + this(exception.getMessage()); + } + } + + private record ErrorMessage(String message) { + } +} diff --git a/src/main/java/com/fcamarasantos/testebackendjava/infra/security/SecurityConfiguration.java b/src/main/java/com/fcamarasantos/testebackendjava/infra/security/SecurityConfiguration.java new file mode 100644 index 00000000..dfa26812 --- /dev/null +++ b/src/main/java/com/fcamarasantos/testebackendjava/infra/security/SecurityConfiguration.java @@ -0,0 +1,31 @@ +package com.fcamarasantos.testebackendjava.infra.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(req -> { + req.requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**").permitAll(); + req.anyRequest().permitAll(); + }) + .build(); + } + +} diff --git a/src/main/resources/application-test.yaml b/src/main/resources/application-test.yaml new file mode 100644 index 00000000..5ca09d59 --- /dev/null +++ b/src/main/resources/application-test.yaml @@ -0,0 +1,9 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:fcamaradatabase;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: + h2: + console: + enabled: true \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 00000000..ed5ba027 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,27 @@ + +spring: + application: + name: testebackendjava + + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/fcamaradatabase + username: localuser + password: localpassword + + jpa: + show-sql: true + properties: + hibernate: + format_sql: true + + flyway: + baseline-on-migrate: true + +server: + error: + include-stacktrace: never + +springdoc: + swagger-ui: + path: /swagger-ui.html \ No newline at end of file diff --git a/src/main/resources/db/migration/V1__create-table-veiculos.sql b/src/main/resources/db/migration/V1__create-table-veiculos.sql new file mode 100644 index 00000000..7a770ff2 --- /dev/null +++ b/src/main/resources/db/migration/V1__create-table-veiculos.sql @@ -0,0 +1,12 @@ + create table veiculos + ( + + id SERIAL not null, + marca varchar(100) not null, + placa varchar(100) not null UNIQUE, + cor varchar(10) not null, + modelo varchar(100) not null, + tipo varchar(20) not null, + primary key (id) + + ); \ No newline at end of file diff --git a/src/main/resources/db/migration/V2__create-table-estabelecimentos.sql b/src/main/resources/db/migration/V2__create-table-estabelecimentos.sql new file mode 100644 index 00000000..e3840d5f --- /dev/null +++ b/src/main/resources/db/migration/V2__create-table-estabelecimentos.sql @@ -0,0 +1,22 @@ +create table estabelecimentos +( + + id SERIAL not null, + nome varchar(100) not null, + cnpj varchar(14) not null UNIQUE, + telefone varchar(15), + num_vagas_motos integer not null, + num_vagas_carros integer not null, + + +-- Endereco embutido no objeto + logradouro varchar(100) not null, + bairro varchar(100) not null, + cep varchar(8) not null, + complemento varchar(100), + numero varchar(20), + uf char(2) not null, + cidade varchar(100) not null, + + primary key (id) +); \ No newline at end of file diff --git a/src/main/resources/db/migration/V3__create-table-registros_entrada_saida.sql b/src/main/resources/db/migration/V3__create-table-registros_entrada_saida.sql new file mode 100644 index 00000000..e15aa944 --- /dev/null +++ b/src/main/resources/db/migration/V3__create-table-registros_entrada_saida.sql @@ -0,0 +1,14 @@ +create table registros_entrada_saida +( + + id SERIAL not null, + estabelecimento_id bigint not null, + veiculo_id bigint not null, + data_entrada timestamp not null, + data_saida timestamp, + + + primary key (id), + constraint fk_registros_entrada_saida_estabelecimento_id foreign key (estabelecimento_id) references estabelecimentos (id), + constraint fk_registros_entrada_saida_veiculo_id foreign key (veiculo_id) references veiculos (id) +); \ No newline at end of file diff --git a/src/test/java/com/fcamarasantos/testebackendjava/TestebackendjavaApplicationTests.java b/src/test/java/com/fcamarasantos/testebackendjava/TestebackendjavaApplicationTests.java new file mode 100644 index 00000000..80977dd8 --- /dev/null +++ b/src/test/java/com/fcamarasantos/testebackendjava/TestebackendjavaApplicationTests.java @@ -0,0 +1,13 @@ +package com.fcamarasantos.testebackendjava; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class TestebackendjavaApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/com/fcamarasantos/testebackendjava/controller/VeiculoControllerTest.java b/src/test/java/com/fcamarasantos/testebackendjava/controller/VeiculoControllerTest.java new file mode 100644 index 00000000..96779259 --- /dev/null +++ b/src/test/java/com/fcamarasantos/testebackendjava/controller/VeiculoControllerTest.java @@ -0,0 +1,85 @@ +package com.fcamarasantos.testebackendjava.controller; + +import com.fcamarasantos.testebackendjava.domain.veiculo.TipoVeiculo; +import com.fcamarasantos.testebackendjava.domain.veiculo.Veiculo; +import com.fcamarasantos.testebackendjava.domain.veiculo.VeiculoRepository; +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.CreateVeiculoDTO; +import com.fcamarasantos.testebackendjava.domain.veiculo.dto.VeiculoDetailsDTO; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.json.AutoConfigureJsonTesters; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.json.JacksonTester; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@SpringBootTest +@AutoConfigureMockMvc +@AutoConfigureJsonTesters +@ActiveProfiles("test") +class VeiculoControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private JacksonTester createVeiculoJson; + + @Autowired + private JacksonTester veiculoDetailsJson; + + @MockBean + private VeiculoRepository veiculoRepository; + + @Test + @DisplayName("Deve retornar 400 quando um dos campos obrigatorios nao forem enviados.") + public void cadastrarVeiculoInvalido() throws Exception { + var veiculoInvalidoJson = createVeiculoJson.write( + new CreateVeiculoDTO("marca-fake", "modelo-fake", "abc-123", "", TipoVeiculo.MOTO) + ); + + var response = mockMvc + .perform(post("/veiculos/") + .contentType(MediaType.APPLICATION_JSON) + .content(veiculoInvalidoJson.getJson())) + .andReturn(); + + assertEquals(response.getResponse().getStatus(), HttpStatus.BAD_REQUEST.value()); + } + + @Test + @DisplayName("Deve retornar 201 quando um veiculo valido for cadastrado.") + public void cadastrarVeiculoValido() throws Exception { + var veiculoValido = new CreateVeiculoDTO("marca-fake", "modelo-fake", "abc-123", "azul", TipoVeiculo.MOTO); + var veiculoValidoJson = createVeiculoJson.write(veiculoValido); + + var veiculo = new Veiculo(veiculoValido); + when(veiculoRepository + .save(any())) + .thenReturn(veiculo); + + var response = mockMvc + .perform(post("/veiculos/") + .contentType(MediaType.APPLICATION_JSON) + .content(veiculoValidoJson.getJson())) + .andReturn(); + + var veiculoEsperadoJson = veiculoDetailsJson.write( + new VeiculoDetailsDTO(veiculo) + ); + + assertEquals(response.getResponse().getStatus(), HttpStatus.CREATED.value()); + assertEquals(response.getResponse().getContentAsString(), veiculoEsperadoJson.getJson()); + } + +} \ No newline at end of file