├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── Dockerfile ├── README.md ├── app.yaml ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── fakestore │ │ └── api │ │ ├── FakeStoreApplication.java │ │ ├── advice │ │ └── CustomControllerAdvice.java │ │ ├── dto │ │ ├── CategoryCreationDTO.java │ │ ├── CategoryResponseDTO.java │ │ ├── ChangePasswordDTO.java │ │ ├── OrderDTO.java │ │ ├── OrderDetailDTO.java │ │ ├── OrderDetailResponseDTO.java │ │ ├── OrderDetailUpdateDTO.java │ │ ├── OrderResponseDTO.java │ │ ├── ProductCreationDTO.java │ │ ├── ProductResponseDTO.java │ │ ├── UserCreationDTO.java │ │ ├── UserResponseDTO.java │ │ ├── UserUpdateDTO.java │ │ └── emailDTO.java │ │ ├── exception │ │ ├── CategoryNotFoundException.java │ │ ├── ProductAlreadyExistsException.java │ │ ├── ProductNotFoundException.java │ │ ├── UserAlreadyExistsException.java │ │ └── UserNotFoundException.java │ │ ├── initialization │ │ └── DataInitializer.java │ │ ├── persistence │ │ ├── entity │ │ │ ├── Category.java │ │ │ ├── Order.java │ │ │ ├── OrderDetail.java │ │ │ ├── OrderStatus.java │ │ │ ├── Product.java │ │ │ ├── Role.java │ │ │ └── User.java │ │ └── repository │ │ │ ├── CategoryRepository.java │ │ │ ├── OrderDetailRepository.java │ │ │ ├── OrderRepository.java │ │ │ ├── ProductRepository.java │ │ │ └── UserRepository.java │ │ ├── security │ │ ├── AuthCredential.java │ │ ├── JWTAuthenticationFilter.java │ │ ├── JWTAuthorizationFilter.java │ │ ├── SecurityConfig.java │ │ ├── TokenUtils.java │ │ ├── UserDetailServiceImpl.java │ │ └── UserDetailsImpl.java │ │ ├── service │ │ ├── CategoryService.java │ │ ├── OrderDetailService.java │ │ ├── OrderService.java │ │ ├── ProductService.java │ │ └── UserService.java │ │ ├── util │ │ └── DateUtil.java │ │ └── web │ │ └── controller │ │ ├── CategoryController.java │ │ ├── OrderController.java │ │ ├── OrderDetailController.java │ │ ├── ProductController.java │ │ └── UserController.java └── resources │ ├── .gcloudignore │ └── db │ └── migration │ ├── V1__initial_database.sql │ ├── V2__Add_unique_constraint_to_email.sql │ ├── V3__Add_Image_To_Categories.sql │ └── V4__Add_Avatar_And_Role_To_Users.sql └── test └── java └── com └── fakestore └── api └── FakeStoreApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Angel-Concha-Layme/Fake-Store-API/6dac9e31aaf86dea2e9d3147b07911838bf8bcbd/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:17 2 | EXPOSE 8080 3 | WORKDIR /app 4 | COPY target/*.jar /app/ 5 | CMD java -jar $(ls /app/*.jar | head -n 1) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FAKE STORE API [closed] 2 | 3 | ## Descripción 4 | 5 | La API de Fake Store puede usarse con cualquier tipo de proyecto que necesite productos, usuarios, categorías, autenticación y usuarios en formato JSON. 6 | 7 | Esta API incluye características como: 8 | 9 | * ✅ Todas las operaciones CRUD 10 | * ✅ API REST 11 | * ✅ Paginación 12 | * ✅ Autenticación con JWT 13 | * ✅ Filtrar productos por categoría, título y rango de precios 14 | * ✅ Crear usuarios y comprobar si ya existen 15 | * ✅ Archivos de Postman e Insomnia 16 | * ✅ Imágenes de productos generadas por IA 17 | 18 | 19 | ## Paquetes del proyecto 20 | 21 | El proyecto está organizado en los siguientes paquetes: 22 | 23 | - **com.fakestore.api.advice**: Contiene los controladores de excepciones. 24 | - **com.fakestore.api.dto**: Contiene los DTOs que se utilizan para la comunicación entre el cliente y el servidor (creación y respuesta). 25 | - **com.fakestore.api.exception**: Contiene las excepciones personalizadas. 26 | - **com.fakestore.api.initialization**: Contiene la clase necesaria para la inicialización de la base de datos (base de datos por defecto). 27 | - **com.fakestore.api.persistence.entity**: Contiene las entidades de la base de datos. 28 | - **com.fakestore.api.persistence.repository**: Contiene los repositorios de las entidades de la base de datos. 29 | - **com.fakestore.api.security**: Contiene las clases necesarias para la seguridad de la aplicación (autenticación y autorización). 30 | - **com.fakestore.api.service**: Contiene las clases de servicio de la aplicación. 31 | - **com.fakestore.api.util**: Contiene las clases de utilidad de la aplicación. 32 | - **com.fakestore.api.web.controller**: Controladores para manejar las solicitudes HTTP de la API REST. 33 | - **com.fakestore.api.web.filter**: Filtros para interceptar las solicitudes HTTP. 34 | - **com.fakestore.api.web.interceptor**: Interceptores de las solicitudes HTTP. 35 | 36 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | runtime: java17 2 | instance_class: F2 -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.2.0 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "$(uname)" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=$(java-config --jre-home) 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME") 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH") 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && 89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="$(which javac)" 94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=$(which readlink) 97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then 98 | if $darwin ; then 99 | javaHome="$(dirname "\"$javaExecutable\"")" 100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" 101 | else 102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")" 103 | fi 104 | javaHome="$(dirname "\"$javaExecutable\"")" 105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin') 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=$(cd "$wdir/.." || exit 1; pwd) 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | # Remove \r in case we run on Windows within Git Bash 164 | # and check out the repository with auto CRLF management 165 | # enabled. Otherwise, we may read lines that are delimited with 166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word 167 | # splitting rules. 168 | tr -s '\r\n' ' ' < "$1" 169 | fi 170 | } 171 | 172 | log() { 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | printf '%s\n' "$1" 175 | fi 176 | } 177 | 178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")") 179 | if [ -z "$BASE_DIR" ]; then 180 | exit 1; 181 | fi 182 | 183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 184 | log "$MAVEN_PROJECTBASEDIR" 185 | 186 | ########################################################################################## 187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 188 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 189 | ########################################################################################## 190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" 191 | if [ -r "$wrapperJarPath" ]; then 192 | log "Found $wrapperJarPath" 193 | else 194 | log "Couldn't find $wrapperJarPath, downloading it ..." 195 | 196 | if [ -n "$MVNW_REPOURL" ]; then 197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 198 | else 199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 200 | fi 201 | while IFS="=" read -r key value; do 202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) 203 | safeValue=$(echo "$value" | tr -d '\r') 204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; 205 | esac 206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 207 | log "Downloading from: $wrapperUrl" 208 | 209 | if $cygwin; then 210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") 211 | fi 212 | 213 | if command -v wget > /dev/null; then 214 | log "Found wget ... using wget" 215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" 216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 218 | else 219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 220 | fi 221 | elif command -v curl > /dev/null; then 222 | log "Found curl ... using curl" 223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" 228 | fi 229 | else 230 | log "Falling back to using Java to download" 231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" 232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" 233 | # For Cygwin, switch paths to Windows format before running javac 234 | if $cygwin; then 235 | javaSource=$(cygpath --path --windows "$javaSource") 236 | javaClass=$(cygpath --path --windows "$javaClass") 237 | fi 238 | if [ -e "$javaSource" ]; then 239 | if [ ! -e "$javaClass" ]; then 240 | log " - Compiling MavenWrapperDownloader.java ..." 241 | ("$JAVA_HOME/bin/javac" "$javaSource") 242 | fi 243 | if [ -e "$javaClass" ]; then 244 | log " - Running MavenWrapperDownloader.java ..." 245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" 246 | fi 247 | fi 248 | fi 249 | fi 250 | ########################################################################################## 251 | # End of extension 252 | ########################################################################################## 253 | 254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file 255 | wrapperSha256Sum="" 256 | while IFS="=" read -r key value; do 257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; 258 | esac 259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" 260 | if [ -n "$wrapperSha256Sum" ]; then 261 | wrapperSha256Result=false 262 | if command -v sha256sum > /dev/null; then 263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then 264 | wrapperSha256Result=true 265 | fi 266 | elif command -v shasum > /dev/null; then 267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then 268 | wrapperSha256Result=true 269 | fi 270 | else 271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." 272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." 273 | exit 1 274 | fi 275 | if [ $wrapperSha256Result = false ]; then 276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 279 | exit 1 280 | fi 281 | fi 282 | 283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 284 | 285 | # For Cygwin, switch paths to Windows format before running java 286 | if $cygwin; then 287 | [ -n "$JAVA_HOME" ] && 288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") 289 | [ -n "$CLASSPATH" ] && 290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH") 291 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") 293 | fi 294 | 295 | # Provide a "standardized" way to retrieve the CLI args that will 296 | # work with both Windows and non-Windows executions. 297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" 298 | export MAVEN_CMD_LINE_ARGS 299 | 300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 301 | 302 | # shellcheck disable=SC2086 # safe args 303 | exec "$JAVACMD" \ 304 | $MAVEN_OPTS \ 305 | $MAVEN_DEBUG_OPTS \ 306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 309 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file 157 | SET WRAPPER_SHA_256_SUM="" 158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B 160 | ) 161 | IF NOT %WRAPPER_SHA_256_SUM%=="" ( 162 | powershell -Command "&{"^ 163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ 164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ 165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ 166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ 167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ 168 | " exit 1;"^ 169 | "}"^ 170 | "}" 171 | if ERRORLEVEL 1 goto error 172 | ) 173 | 174 | @REM Provide a "standardized" way to retrieve the CLI args that will 175 | @REM work with both Windows and non-Windows executions. 176 | set MAVEN_CMD_LINE_ARGS=%* 177 | 178 | %MAVEN_JAVA_EXE% ^ 179 | %JVM_CONFIG_MAVEN_PROPS% ^ 180 | %MAVEN_OPTS% ^ 181 | %MAVEN_DEBUG_OPTS% ^ 182 | -classpath %WRAPPER_JAR% ^ 183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 185 | if ERRORLEVEL 1 goto error 186 | goto end 187 | 188 | :error 189 | set ERROR_CODE=1 190 | 191 | :end 192 | @endlocal & set ERROR_CODE=%ERROR_CODE% 193 | 194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 198 | :skipRcPost 199 | 200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 202 | 203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 204 | 205 | cmd /C exit /B %ERROR_CODE% 206 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.1 9 | 10 | 11 | com.fakestore 12 | fakestore-api 13 | 0.0.1-SNAPSHOT 14 | Fake Store 15 | API for Fake Store providing CRUD operations, authentication, filtering, and file upload capabilities 16 | 17 | 17 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-jpa 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-security 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-validation 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-web 35 | 36 | 37 | org.flywaydb 38 | flyway-core 39 | 40 | 41 | org.flywaydb 42 | flyway-mysql 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-devtools 48 | runtime 49 | true 50 | 51 | 52 | com.mysql 53 | mysql-connector-j 54 | runtime 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | true 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | org.springframework.restdocs 68 | spring-restdocs-mockmvc 69 | test 70 | 71 | 72 | org.springframework.security 73 | spring-security-test 74 | test 75 | 76 | 77 | io.jsonwebtoken 78 | jjwt-api 79 | 0.12.3 80 | 81 | 82 | io.jsonwebtoken 83 | jjwt-impl 84 | 0.12.3 85 | runtime 86 | 87 | 88 | io.jsonwebtoken 89 | jjwt-jackson 90 | 0.12.3 91 | runtime 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.asciidoctor 99 | asciidoctor-maven-plugin 100 | 2.2.1 101 | 102 | 103 | generate-docs 104 | prepare-package 105 | 106 | process-asciidoc 107 | 108 | 109 | html 110 | book 111 | 112 | 113 | 114 | 115 | 116 | org.springframework.restdocs 117 | spring-restdocs-asciidoctor 118 | ${spring-restdocs.version} 119 | 120 | 121 | 122 | 123 | org.springframework.boot 124 | spring-boot-maven-plugin 125 | 126 | 127 | 128 | org.projectlombok 129 | lombok 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/FakeStoreApplication.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class FakeStoreApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(FakeStoreApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/advice/CustomControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.advice; 2 | 3 | import com.fakestore.api.exception.ProductAlreadyExistsException; 4 | import com.fakestore.api.exception.ProductNotFoundException; 5 | import com.fakestore.api.exception.UserAlreadyExistsException; 6 | import com.fakestore.api.exception.UserNotFoundException; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.ExceptionHandler; 10 | import org.springframework.web.bind.annotation.RestControllerAdvice; 11 | 12 | @RestControllerAdvice 13 | public class CustomControllerAdvice { 14 | @ExceptionHandler(UserNotFoundException.class) 15 | public ResponseEntity handleUserNotFoundException(UserNotFoundException ex){ 16 | return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); 17 | } 18 | 19 | @ExceptionHandler(UserAlreadyExistsException.class) 20 | public ResponseEntity handleUserAlreadyExistsException(UserAlreadyExistsException ex){ 21 | return new ResponseEntity<>(ex.getMessage(), HttpStatus.CONFLICT); 22 | } 23 | 24 | @ExceptionHandler(ProductNotFoundException.class) 25 | public ResponseEntity handleProductNotFoundException(ProductNotFoundException ex){ 26 | return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND); 27 | } 28 | 29 | @ExceptionHandler(ProductAlreadyExistsException.class) 30 | public ResponseEntity handleProductAlreadyExistsException(ProductAlreadyExistsException ex){ 31 | return new ResponseEntity<>(ex.getMessage(), HttpStatus.CONFLICT); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/CategoryCreationDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | 5 | public record CategoryCreationDTO( 6 | @NotNull 7 | String name, 8 | @NotNull 9 | String description 10 | ) { 11 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/CategoryResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record CategoryResponseDTO( 4 | Long id, 5 | String name, 6 | String image, 7 | String description 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/ChangePasswordDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record ChangePasswordDTO( 4 | String currentPassword, 5 | String newPassword, 6 | String confirmNewPassword 7 | ) { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/OrderDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | import com.fakestore.api.persistence.entity.OrderStatus; 4 | import com.fakestore.api.persistence.entity.User; 5 | 6 | import java.time.LocalDate; 7 | import java.util.List; 8 | 9 | public record OrderDTO( 10 | List orderDetails 11 | ) { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/OrderDetailDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record OrderDetailDTO( 4 | Long id, 5 | Integer quantity 6 | ) { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/OrderDetailResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record OrderDetailResponseDTO( 4 | Long id, 5 | Long orderId, 6 | Long productId, 7 | Integer quantity, 8 | Double unitPrice 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/OrderDetailUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record OrderDetailUpdateDTO( 4 | Long productId, 5 | Integer quantity 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/OrderResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record OrderResponseDTO( 4 | Long id, 5 | String orderDate, 6 | String orderStatus, 7 | Double total 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/ProductCreationDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record ProductCreationDTO( 4 | String name, 5 | String description, 6 | Double price, 7 | Integer stockQuantity, 8 | Long categoryId, 9 | String imageUrl) { 10 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/ProductResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | import java.time.LocalDate; 4 | 5 | public record ProductResponseDTO( 6 | Long id, 7 | String name, 8 | String description, 9 | Double price, 10 | Integer stockQuantity, 11 | String categoryName, 12 | String imageUrl, 13 | LocalDate createdAt, 14 | LocalDate updatedAt 15 | ) { 16 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/UserCreationDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | import com.fakestore.api.persistence.entity.Role; 4 | import jakarta.validation.constraints.Email; 5 | import jakarta.validation.constraints.NotNull; 6 | import jakarta.validation.constraints.Size; 7 | 8 | import java.time.LocalDate; 9 | 10 | public record UserCreationDTO( 11 | @NotNull 12 | @Size(min = 3, max = 50) 13 | String username, 14 | @NotNull 15 | @Size(min = 8) 16 | String password, 17 | @Email 18 | @NotNull 19 | String email, 20 | 21 | String avatar, 22 | Role role 23 | ) { 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/UserResponseDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | import com.fakestore.api.persistence.entity.Role; 4 | 5 | import java.time.LocalDate; 6 | 7 | public record UserResponseDTO( 8 | Long id, 9 | String username, 10 | String email, 11 | String avatar, 12 | Role role, 13 | LocalDate createdAt 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/UserUpdateDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | import jakarta.validation.constraints.Email; 4 | import jakarta.validation.constraints.NotNull; 5 | import jakarta.validation.constraints.Size; 6 | 7 | public record UserUpdateDTO( 8 | @NotNull 9 | @Size(min = 3, max = 50) 10 | String username, 11 | @Email 12 | @NotNull 13 | String email 14 | ) { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/dto/emailDTO.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.dto; 2 | 3 | public record emailDTO( 4 | String email 5 | ) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/exception/CategoryNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.exception; 2 | 3 | public class CategoryNotFoundException extends RuntimeException { 4 | public CategoryNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/exception/ProductAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.exception; 2 | 3 | public class ProductAlreadyExistsException extends RuntimeException { 4 | public ProductAlreadyExistsException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/exception/ProductNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.exception; 2 | 3 | public class ProductNotFoundException extends RuntimeException { 4 | public ProductNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/exception/UserAlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.exception; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Size; 5 | 6 | public class UserAlreadyExistsException extends RuntimeException{ 7 | public UserAlreadyExistsException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.exception; 2 | 3 | public class UserNotFoundException extends RuntimeException { 4 | public UserNotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/initialization/DataInitializer.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.initialization; 2 | 3 | 4 | import com.fakestore.api.persistence.entity.Category; 5 | import com.fakestore.api.persistence.entity.Product; 6 | import com.fakestore.api.persistence.entity.Role; 7 | import com.fakestore.api.persistence.entity.User; 8 | import com.fakestore.api.persistence.repository.CategoryRepository; 9 | import com.fakestore.api.persistence.repository.ProductRepository; 10 | import com.fakestore.api.persistence.repository.UserRepository; 11 | import jakarta.transaction.Transactional; 12 | import lombok.AllArgsConstructor; 13 | import org.springframework.beans.factory.InitializingBean; 14 | import org.springframework.security.crypto.password.PasswordEncoder; 15 | import org.springframework.stereotype.Component; 16 | 17 | 18 | // Esta clase se encarga de inicializar los datos de la base de datos 19 | // Para verificar que no existen datos en la base de datos, se usarán métodos de los repositorios 20 | // Si no existen datos, se inicializarán los datos de la base de datos 21 | // Los datos que se inicializarán son: 22 | // - Usuarios: User 23 | // - Categorías: Category 24 | // - Productos: Product 25 | // - Pedidos: Order 26 | 27 | // Se tienen que inicializar los datos de la base de datos en el orden indicado anteriormente, por sus dependencias 28 | 29 | @Component 30 | @AllArgsConstructor 31 | public class DataInitializer implements InitializingBean { 32 | private final UserRepository userRepository; 33 | private final CategoryRepository categoryRepository; 34 | private final ProductRepository productRepository; 35 | private final PasswordEncoder passwordEncoder; 36 | 37 | @Transactional 38 | @Override 39 | public void afterPropertiesSet() throws Exception { 40 | 41 | // Usuarios 42 | createUserIfNotExists("Juan", "juan@gmail.com","https://i.ibb.co/3RT7L1p/Juan.jpg"); 43 | createUserIfNotExists("Manuel", "manuel@gmail.com","https://i.ibb.co/mqmFQfn/Manuel.jpg"); 44 | createUserIfNotExists("Laura", "laura@gmail.com","https://i.ibb.co/0XpLV6c/laura.jpg"); 45 | createUserIfNotExists("Carlos", "carlos@gmail.com", "https://i.ibb.co/VVDKfc5/Carlos.jpg"); 46 | createUserIfNotExists("Maria", "maria@gmail.com", "https://i.ibb.co/syJ4yLh/Maria.jpg"); 47 | createUserIfNotExists("Jose", "jose@gmail.com","https://i.ibb.co/DK51xM3/Jose.jpg"); 48 | createUserIfNotExists("Ana", "ana@gmail.com","https://i.ibb.co/Rz7LFjJ/Ana.jpg" ); 49 | createUserIfNotExists("David", "david@gmail.com","https://i.ibb.co/w7SfCdf/David.jpg"); 50 | createUserIfNotExists("Sara", "sara@gmail.com","https://i.ibb.co/0MBZhrG/Sara.jpg"); 51 | createUserIfNotExists("Pedro", "pedro@gmail.com","https://i.ibb.co/kMGxZch/Pedro.jpg" ); 52 | 53 | // Categorías 54 | createCategoryIfNotExists("Electrónica", "Productos relacionados con dispositivos electrónicos, como smartphones, televisores, computadoras y más.","https://i.ibb.co/1QzkGf7/Electronica.png" ); 55 | createCategoryIfNotExists("Moda", "Prendas y accesorios para todas las edades y estilos, incluyendo ropa casual, formal, deportiva y de temporada.","https://i.ibb.co/Chjv3k8/Moda.png"); 56 | createCategoryIfNotExists("Alimentación", "Variedad de productos comestibles, incluyendo alimentos frescos, envasados, orgánicos, y especialidades gourmet.","https://i.ibb.co/hLpVpZX/Alimentaci-n.png"); 57 | createCategoryIfNotExists("Hogar y Jardín", "Artículos para mejorar y embellecer tu espacio de vida, incluyendo muebles, decoración, herramientas de jardinería y electrodomésticos.","https://i.ibb.co/s2GpH7J/Hogar-y-jardin.png"); 58 | createCategoryIfNotExists("Salud y Belleza", "Productos para el cuidado personal y bienestar, incluyendo cosméticos, productos de higiene, suplementos y equipos de fitness.","https://i.ibb.co/ZSsFGNL/Salud-y-belleza.png"); 59 | createCategoryIfNotExists("Juguetes y Juegos", "Amplia gama de juguetes y juegos para niños y adultos, incluyendo juegos de mesa, juguetes educativos y electrónicos.","https://i.ibb.co/pr82Q23/Juguetes-y-juegos.png"); 60 | createCategoryIfNotExists("Libros y Papelería", "Una diversa colección de libros, revistas, material de papelería y accesorios de escritura para todas las edades y gustos.","https://i.ibb.co/QjP1Zk0/Libros-y-papeleria.png"); 61 | createCategoryIfNotExists("Deportes y Aire Libre", "Equipo y accesorios para una variedad de actividades deportivas y al aire libre, desde ciclismo hasta camping y fitness.", "https://i.ibb.co/VLCFsDF/Deportes-y-aire-libre.png"); 62 | createCategoryIfNotExists("Mascotas", "Todo lo necesario para el cuidado de mascotas, incluyendo alimentos, juguetes, accesorios y productos de salud.", "https://i.ibb.co/8sQh7qw/Mascotas.png"); 63 | createCategoryIfNotExists("Tecnología y Accesorios", "Productos innovadores y accesorios en el campo de la tecnología, incluyendo gadgets, dispositivos de audio y video, y periféricos de computadora.", "https://i.ibb.co/3Bdp2BR/Tecnologia-y-accesorios.png"); 64 | 65 | // Productos 66 | createProductIfNotExists("Portátil Apple MacBook Pro", "Portátil de alto rendimiento con pantalla Retina de 13 pulgadas, chip Apple M1, 8 GB de RAM y 256 GB de SSD.", 1299.99, 75, 1, "https://i.ibb.co/cDj3dpX/Apple-Mac-Book-Pro.png"); 67 | createProductIfNotExists("Smartphone Samsung Galaxy S21", "Smartphone con pantalla AMOLED de 6.2 pulgadas, cámara triple, 8 GB de RAM y 128 GB de almacenamiento.", 899.99, 50, 1, "https://i.ibb.co/mXkVjdS/Smartphone-Samsung-Galaxy-S21.png"); 68 | createProductIfNotExists("Cámara Sony Alpha A7 III", "Cámara mirrorless con sensor full-frame de 24.2 MP, grabación de vídeo 4K HDR y sistema de enfoque automático rápido.", 1999.99, 30, 1, "https://i.ibb.co/ZHT2r1s/C-mara-Sony-Alpha-A7-III.png"); 69 | createProductIfNotExists("Consola PlayStation 5", "Consola de juegos de última generación con gráficos en 4K, SSD ultrarrápido y retrocompatibilidad con juegos de PS4.", 499.99, 100, 1, "https://i.ibb.co/BcqzzgF/Consola-Play-Station-5.png"); 70 | createProductIfNotExists("Auriculares Inalámbricos Bose QuietComfort 35", "Auriculares con cancelación de ruido, conexión Bluetooth, y hasta 20 horas de duración de batería.", 299.99, 40, 1, "https://i.ibb.co/YTb1Ybv/Auriculares-Inal-mbricos-Bose-Quiet-Comfort-35.png"); 71 | 72 | createProductIfNotExists("Camiseta Nike Dri-FIT", "Camiseta deportiva de alto rendimiento con tecnología Dri-FIT para mantenerse seco y cómodo.", 39.99, 100, 2, "https://i.ibb.co/Hpv1gLk/Camiseta-Nike-Dri-FIT.png"); 73 | createProductIfNotExists("Vestido Elegante de Noche", "Vestido largo elegante ideal para eventos nocturnos, disponible en varios colores y tallas.", 129.99, 30, 2, "https://i.ibb.co/WVY7wyD/Vestido-Elegante-de-Noche.png"); 74 | createProductIfNotExists("Jeans Levi's 501 Original", "Jeans clásicos de corte recto, disponibles en una variedad de lavados y tallas.", 59.99, 80, 2, "https://i.ibb.co/yn9tWB2/Jeans-Levi-s-501-Original.png"); 75 | createProductIfNotExists("Zapatillas Adidas Ultraboost", "Zapatillas de running con tecnología Boost para una mayor amortiguación y comodidad.", 179.99, 50, 2, "https://i.ibb.co/7jKp0rs/Zapatillas-Adidas-Ultraboost.png"); 76 | createProductIfNotExists("Chaqueta de Cuero Estilo Motociclista", "Chaqueta de cuero genuino, ideal para un look casual o para motociclismo.", 199.99, 25, 2, "https://i.ibb.co/0hXWpfn/Chaqueta-de-Cuero-Estilo-Motociclista.png"); 77 | 78 | createProductIfNotExists("Caja de Chocolates Gourmet", "Selección de chocolates finos de diferentes sabores y orígenes, presentados en una caja elegante.", 49.99, 60, 3, "https://i.ibb.co/j8TTvTk/Caja-de-Chocolates-Gourmet.png"); 79 | createProductIfNotExists("Café Colombiano Premium", "Café de alta calidad, con notas frutales y un toque de dulzura, cultivado en las montañas de Colombia.", 19.99, 100, 3, "https://i.ibb.co/Gnrc0xc/Caf-Colombiano-Premium.png"); 80 | createProductIfNotExists("Aceite de Oliva Virgen Extra", "Aceite de oliva puro, prensado en frío, ideal para cocinar y aderezar ensaladas.", 14.99, 80, 3, "https://i.ibb.co/7p3zhsR/Aceite-de-Oliva-Virgen-Extra.png"); 81 | createProductIfNotExists("Surtido de Frutas Orgánicas", "Caja de frutas orgánicas frescas, incluyendo manzanas, naranjas, plátanos, y más.", 29.99, 50, 3, "https://i.ibb.co/Z8zRpNL/Surtido-de-Frutas-Org-nicas.png"); 82 | createProductIfNotExists("Set de Especias del Mundo", "Colección de especias de varias partes del mundo, perfectas para experimentar con diferentes sabores en la cocina.", 34.99, 40, 3, "https://i.ibb.co/2F8vG7d/Set-de-Especias-del-Mundo.png"); 83 | 84 | createProductIfNotExists("Sofá Seccional Moderno", "Sofá amplio y cómodo, perfecto para salas de estar, disponible en varios colores.", 899.99, 20, 4, "https://i.ibb.co/KNKVQ02/Sof-Seccional-Moderno.png"); 85 | createProductIfNotExists("Set de Herramientas de Jardinería", "Kit completo con herramientas esenciales para el cuidado del jardín, incluyendo palas, tijeras y guantes.", 59.99, 50, 4, "https://i.ibb.co/kgKgWhP/Set-de-Herramientas-de-Jardiner-a.png"); 86 | createProductIfNotExists("Lámpara de Pie Estilo Moderno", "Lámpara de pie elegante para iluminación ambiental, ideal para cualquier espacio del hogar.", 129.99, 40, 4, "https://i.ibb.co/CWvLBtk/L-mpara-de-Pie-Estilo-Moderno.png"); 87 | createProductIfNotExists("Robot Aspirador Inteligente", "Aspiradora robotizada para limpieza automática de pisos, controlable vía smartphone.", 299.99, 30, 4, "https://i.ibb.co/YjzF2VM/Robot-Aspirador-Inteligente.png"); 88 | createProductIfNotExists("Juego de Sábanas de Algodón Egipcio", "Juego de sábanas de alta calidad, suaves y duraderas, para camas de tamaño queen.", 99.99, 60, 4, "https://i.ibb.co/ryfPRZW/Juego-de-S-banas-de-Algod-n-Egipcio.png"); 89 | 90 | createProductIfNotExists("Set de Maquillaje Profesional", "Kit completo de maquillaje con variedad de sombras, labiales, y más.", 79.99, 40, 5, "https://i.ibb.co/Br5FQWy/Set-de-Maquillaje-Profesional.png"); 91 | createProductIfNotExists("Crema Hidratante Facial Orgánica", "Crema facial orgánica para todo tipo de piel, hidrata y rejuvenece.", 29.99, 70, 5, "https://i.ibb.co/MhFdBtN/Crema-Hidratante-Facial-Org-nica.png"); 92 | createProductIfNotExists("Cepillo de Dientes Eléctrico", "Cepillo eléctrico con tecnología avanzada para una limpieza profunda.", 59.99, 50, 5, "https://i.ibb.co/Cs5s2C0/Cepillo-de-Dientes-El-ctrico.png"); 93 | createProductIfNotExists("Suplementos Multivitamínicos", "Multivitamínicos para apoyar la salud general y el bienestar.", 19.99, 100, 5, "https://i.ibb.co/zP89hQn/Suplementos-Multivitam-nicos.png"); 94 | createProductIfNotExists("Kit de Yoga con Mat y Accesorios", "Set completo para practicar yoga, incluye mat, bloques y correa.", 49.99, 30, 5, "https://i.ibb.co/MPPRvxp/Kit-de-Yoga-con-Mat-y-Accesorios.png"); 95 | 96 | createProductIfNotExists("Juego de Mesa Monopoly", "Clásico juego de mesa Monopoly para toda la familia.", 24.99, 40, 6, "https://i.ibb.co/gPfxMfH/Juego-de-Mesa-Monopoly.png"); 97 | createProductIfNotExists("Conjunto de Trenes de Madera", "Set de trenes de madera para niños, estimula la creatividad y la coordinación.", 34.99, 50, 6, "https://i.ibb.co/FwZ3PC1/Conjunto-de-Trenes-de-Madera.png"); 98 | createProductIfNotExists("Consola de Videojuegos Retro", "Consola con juegos clásicos incorporados, ideal para nostálgicos.", 59.99, 30, 6, "https://i.ibb.co/jT8GgNT/Consola-de-Videojuegos-Retro.png"); 99 | createProductIfNotExists("Muñeca Barbie Edición Especial", "Barbie edición especial coleccionable, con accesorios de moda.", 29.99, 25, 6, "https://i.ibb.co/2yXpcBV/Mu-eca-Barbie-Edici-n-Especial.png"); 100 | createProductIfNotExists("Rompecabezas 3D de la Torre Eiffel", "Puzzle 3D de la Torre Eiffel, desafío divertido y educativo.", 19.99, 60, 6, "https://i.ibb.co/zV9DKgz/Rompecabezas-3-D-de-la-Torre-Eiffel.png"); 101 | 102 | createProductIfNotExists("Novela 'Cien Años de Soledad'", "Famosa novela de Gabriel García Márquez, una obra maestra de la literatura.", 12.99, 80, 7, "https://i.ibb.co/KKmtYhr/Novela-Cien-A-os-de-Soledad.png"); 103 | createProductIfNotExists("Set de Artículos de Papelería", "Kit completo de papelería incluyendo bolígrafos, cuadernos y más.", 24.99, 50, 7, "https://i.ibb.co/Nt4LsYK/Set-de-Art-culos-de-Papeler-a.png"); 104 | createProductIfNotExists("Agenda Personal 2023", "Agenda elegante para organizar tu día a día, con espacio para notas y calendario.", 9.99, 100, 7, "https://i.ibb.co/WgKt7vW/Agenda-Personal-2023.png"); 105 | createProductIfNotExists("Libro de Cocina 'Recetas del Mundo'", "Libro de recetas con platos internacionales, ilustraciones y guías paso a paso.", 29.99, 40, 7, "https://i.ibb.co/f8qMcL3/Libro-de-Cocina-Recetas-del-Mundo.png"); 106 | createProductIfNotExists("Set de Rotuladores de Colores", "Colección de rotuladores de colores de alta calidad, perfectos para dibujo y diseño.", 19.99, 60, 7, "https://i.ibb.co/k4G1B7B/Set-de-Rotuladores-de-Colores.png"); 107 | 108 | createProductIfNotExists("Bicicleta de Montaña", "Bicicleta robusta para terrenos difíciles, perfecta para aventuras al aire libre.", 299.99, 20, 8, "https://i.ibb.co/4NN13XR/Bicicleta-de-Monta-a.png"); 109 | createProductIfNotExists("Carpa de Camping para 4 Personas", "Carpa espaciosa y resistente, ideal para camping familiar.", 199.99, 15, 8, "https://i.ibb.co/J2YSrjv/Carpa-de-Camping-para-4-Personas.png"); 110 | createProductIfNotExists("Balón de Fútbol Adidas", "Balón de fútbol oficial tamaño y peso reglamentario, duradero y de alta calidad.", 29.99, 50, 8, "https://i.ibb.co/yNnbKvb/Bal-n-de-F-tbol-Adidas.png"); 111 | createProductIfNotExists("Set de Pesca Completo", "Equipo de pesca con caña, carrete, anzuelos y más, perfecto para pescadores de todos los niveles.", 99.99, 25, 8, "https://i.ibb.co/9ykW5p5/Set-de-Pesca-Completo.png"); 112 | createProductIfNotExists("Patineta Eléctrica", "Patineta eléctrica de alta velocidad y larga duración de batería, ideal para transporte urbano.", 399.99, 10, 8, "https://i.ibb.co/cCCrxtc/Patineta-El-ctrica.png"); 113 | 114 | createProductIfNotExists("Alimento Seco para Perros", "Alimento balanceado y nutritivo para perros de todas las edades y tamaños.", 49.99, 100, 9, "https://i.ibb.co/3fQvm0s/Alimento-Seco-para-Perros.png"); 115 | createProductIfNotExists("Rascador para Gatos", "Rascador de varios niveles para gatos, incluye juguetes y camas.", 69.99, 30, 9, "https://i.ibb.co/gmvyKJy/Rascador-para-Gatos.png"); 116 | createProductIfNotExists("Acuario con Kit Completo", "Acuario de 20 litros con filtro, iluminación y decoración, ideal para principiantes.", 129.99, 20, 9, "https://i.ibb.co/cvFYrzP/Acuario-con-Kit-Completo.png"); 117 | createProductIfNotExists("Correa Retráctil para Perros", "Correa retráctil de alta calidad, segura y cómoda para paseos diarios.", 19.99, 50, 9, "https://i.ibb.co/8j45qb3/Correa-Retr-ctil-para-Perros.png"); 118 | createProductIfNotExists("Juguete Interactivo para Mascotas", "Juguete estimulante para mascotas, mantiene a los animales activos y entretenidos.", 14.99, 40, 9, "https://i.ibb.co/27qzrYw/Juguete-Interactivo-para-Mascotas.png"); 119 | 120 | createProductIfNotExists("Smartwatch Avanzado Modelo X","Reloj inteligente con seguimiento de actividad física, notificaciones móviles, y resistencia al agua.",299.99,50,10,"https://i.ibb.co/WG8bmBJ/Smartwatch-Avanzado-Modelo-X.png"); 121 | createProductIfNotExists("Drone con Cámara 4K","Drone equipado con cámara 4K y tecnología de estabilización de imagen, perfecto para fotografía aérea y videografía.",499.99,30,10,"https://i.ibb.co/hsHMZkr/Drone-con-C-mara-4-K.png"); 122 | createProductIfNotExists("Teclado Mecánico Retroiluminado","Teclado mecánico de alta calidad con retroiluminación LED y teclas personalizables para una experiencia de escritura superior.",129.99,60,10,"https://i.ibb.co/dmwyFRg/Teclado-Mec-nico-Retroiluminado.png"); 123 | createProductIfNotExists("Auriculares de Realidad Virtual","Experiencia inmersiva con estos auriculares de realidad virtual, compatibles con una amplia gama de juegos y aplicaciones VR.",399.99,40,10,"https://i.ibb.co/2jpCmsB/Auriculares-de-Realidad-Virtual.png"); 124 | createProductIfNotExists("Altavoz Bluetooth Portátil","Altavoz Bluetooth compacto y portátil con excelente calidad de sonido y resistencia al agua, ideal para llevarlo a cualquier parte.",99.99,75,10,"https://i.ibb.co/LPYY3vD/Altavoz-Bluetooth-Port-til.png"); 125 | 126 | } 127 | 128 | private void createUserIfNotExists(String username, String email, String avatar) { 129 | if (!userRepository.existsByEmail(email)) { 130 | User user = new User(); 131 | user.setUsername(username); 132 | user.setPassword(passwordEncoder.encode(username + "1234")); 133 | user.setEmail(email); 134 | user.setAvatar(avatar); 135 | user.setRole(Role.CUSTOMER); 136 | user.setCreatedAt(java.time.LocalDate.now()); 137 | userRepository.save(user); 138 | } 139 | } 140 | 141 | private void createCategoryIfNotExists(String name, String description, String image) { 142 | if (!categoryRepository.existsByName(name)) { 143 | Category category = new Category(); 144 | category.setName(name); 145 | category.setDescription(description); 146 | category.setImage(image); 147 | categoryRepository.save(category); 148 | } 149 | } 150 | private void createProductIfNotExists(String name, String description, double price, int stockQuantity, long categoryId, String imageUrl) { 151 | if (!productRepository.existsByName(name)) { 152 | Product product = new Product(); 153 | product.setName(name); 154 | product.setDescription(description); 155 | product.setPrice(price); 156 | product.setStockQuantity(stockQuantity); 157 | Category category = categoryRepository.findById(categoryId).orElseThrow(() -> new RuntimeException("Categoría no encontrada")); 158 | product.setCategory(category); 159 | product.setImageUrl(imageUrl); 160 | product.setCreatedAt(java.time.LocalDate.now()); 161 | product.setUpdatedAt(java.time.LocalDate.now()); 162 | productRepository.save(product); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/Category.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | import java.util.List; 11 | 12 | @Entity 13 | @Table(name = "categories") 14 | @Getter 15 | @Setter 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class Category { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | 23 | private String name; 24 | 25 | private String description; 26 | 27 | private String image; 28 | 29 | @OneToMany(mappedBy = "category") 30 | @JsonIgnore 31 | private List products; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/Order.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | 4 | import jakarta.persistence.*; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | import java.time.LocalDate; 11 | import java.util.List; 12 | 13 | @Entity 14 | @Table(name = "orders") 15 | @Setter 16 | @Getter 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class Order { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | 24 | @ManyToOne 25 | @JoinColumn(name = "user_id") 26 | private User user; 27 | 28 | @Column(name = "order_date") 29 | private LocalDate orderDate; 30 | 31 | @Column(name = "order_status") 32 | @Enumerated(EnumType.STRING) 33 | private OrderStatus orderStatus; 34 | 35 | private Double total; 36 | 37 | @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) 38 | private List orderDetails; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/OrderDetail.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Entity 10 | @Table(name = "order_details") 11 | @Setter 12 | @Getter 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class OrderDetail { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | private Long id; 19 | 20 | @ManyToOne 21 | @JoinColumn(name = "order_id") 22 | private Order order; 23 | 24 | @ManyToOne 25 | @JoinColumn(name = "product_id") 26 | private Product product; 27 | 28 | private Integer quantity; 29 | 30 | @Column(name = "unit_price") 31 | private Double unitPrice; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | public enum OrderStatus { 4 | PENDING, 5 | COMPLETED, 6 | CANCELLED 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/Product.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import java.time.LocalDate; 10 | import java.util.List; 11 | 12 | @Entity 13 | @Table(name = "products") 14 | @Getter 15 | @Setter 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class Product { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | 23 | private String name; 24 | 25 | private String description; 26 | 27 | private Double price; 28 | 29 | @Column(name = "stock_quantity") 30 | private Integer stockQuantity; 31 | 32 | @ManyToOne 33 | @JoinColumn(name = "category_id") 34 | private Category category; 35 | 36 | @Column(name = "image_url") 37 | private String imageUrl; 38 | 39 | @Column(name = "created_at") 40 | private LocalDate createdAt; 41 | 42 | @Column(name = "updated_at") 43 | private LocalDate updatedAt; 44 | 45 | @OneToMany(mappedBy = "product") 46 | private List orderDetails; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | public enum Role { 4 | ADMIN, CUSTOMER 5 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | import java.time.LocalDate; 11 | import java.util.List; 12 | 13 | @Entity 14 | @Table(name = "users") 15 | @Getter 16 | @Setter 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class User { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | 24 | private String username; 25 | 26 | private String password; 27 | 28 | private String email; 29 | 30 | private String avatar; 31 | 32 | @Enumerated(EnumType.STRING) 33 | private Role role; 34 | 35 | @Column(name = "created_at") 36 | private LocalDate createdAt; 37 | 38 | @JsonIgnore 39 | @OneToMany(mappedBy = "user") 40 | private List orders; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.repository; 2 | 3 | import com.fakestore.api.persistence.entity.Category; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface CategoryRepository extends JpaRepository { 11 | boolean existsByName(String name); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/repository/OrderDetailRepository.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.repository; 2 | 3 | import com.fakestore.api.persistence.entity.OrderDetail; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface OrderDetailRepository extends JpaRepository { 11 | List findAllByOrder_Id(Long id); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.repository; 2 | 3 | import com.fakestore.api.persistence.entity.Order; 4 | import com.fakestore.api.persistence.entity.User; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.List; 11 | 12 | @Repository 13 | public interface OrderRepository extends JpaRepository { 14 | Page findAllByUser(User user, Pageable pageable); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/repository/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.repository; 2 | 3 | import com.fakestore.api.persistence.entity.Product; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.domain.Specification; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.Optional; 11 | 12 | @Repository 13 | public interface ProductRepository extends JpaRepository { 14 | 15 | Optional findByName(String name); 16 | 17 | boolean existsByName(String name); 18 | 19 | Page findAll(Specification spec, Pageable pageable); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/persistence/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.persistence.repository; 2 | 3 | import com.fakestore.api.persistence.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface UserRepository extends JpaRepository { 11 | Optional findByUsername(String username); 12 | Optional findOneByEmail(String email); 13 | 14 | boolean existsByEmail(String email); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/AuthCredential.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class AuthCredential { 7 | private String email; 8 | private String password; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/JWTAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import jakarta.servlet.FilterChain; 5 | import jakarta.servlet.ServletException; 6 | import jakarta.servlet.http.HttpServletRequest; 7 | import jakarta.servlet.http.HttpServletResponse; 8 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.AuthenticationException; 11 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 12 | 13 | import java.io.IOException; 14 | import java.util.Collections; 15 | 16 | public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { 17 | @Override 18 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 19 | 20 | AuthCredential authCredential = new AuthCredential(); 21 | 22 | try{ 23 | authCredential = new ObjectMapper().readValue(request.getReader(), AuthCredential.class); 24 | } catch (IOException ignored){ 25 | } 26 | 27 | UsernamePasswordAuthenticationToken usernamePAT = new UsernamePasswordAuthenticationToken( 28 | authCredential.getEmail(), 29 | authCredential.getPassword(), 30 | Collections.emptyList() 31 | ); 32 | 33 | return getAuthenticationManager().authenticate(usernamePAT); 34 | } 35 | 36 | @Override 37 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 38 | 39 | UserDetailsImpl userDetails = (UserDetailsImpl) authResult.getPrincipal(); 40 | String token = TokenUtils.createToken(userDetails.getFullName(), userDetails.getUsername()); // TODO: change way to get username 41 | 42 | response.addHeader("Authorization", "Bearer " + token); 43 | response.getWriter().flush(); 44 | 45 | //super.successfulAuthentication(request, response, chain, authResult); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/JWTAuthorizationFilter.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | import jakarta.servlet.FilterChain; 4 | import jakarta.servlet.ServletException; 5 | import jakarta.servlet.http.HttpServletRequest; 6 | import jakarta.servlet.http.HttpServletResponse; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.filter.OncePerRequestFilter; 11 | 12 | import java.io.IOException; 13 | 14 | @Component 15 | public class JWTAuthorizationFilter extends OncePerRequestFilter { 16 | 17 | @Override 18 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 19 | String bearerToken = request.getHeader("Authorization"); 20 | if (bearerToken != null && bearerToken.startsWith("Bearer ")) { 21 | String token = bearerToken.replace("Bearer ", ""); // TODO: Check if this is the correct way to do it 22 | UsernamePasswordAuthenticationToken authentication = TokenUtils.getAuthentication(token); 23 | SecurityContextHolder.getContext().setAuthentication(authentication); 24 | } 25 | filterChain.doFilter(request, response); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.authentication.ProviderManager; 9 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 12 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 13 | import org.springframework.security.core.userdetails.User; 14 | import org.springframework.security.core.userdetails.UserDetails; 15 | import org.springframework.security.core.userdetails.UserDetailsService; 16 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 19 | import org.springframework.security.web.SecurityFilterChain; 20 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 21 | import org.springframework.security.web.session.SessionManagementFilter; 22 | import org.springframework.web.cors.CorsConfiguration; 23 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 24 | import org.springframework.web.filter.CorsFilter; 25 | 26 | import java.util.Arrays; 27 | 28 | import static org.springframework.security.config.Customizer.withDefaults; 29 | 30 | 31 | @Configuration 32 | @EnableWebSecurity 33 | @AllArgsConstructor 34 | public class SecurityConfig { 35 | 36 | private final UserDetailsService userDetailsService; 37 | private final JWTAuthorizationFilter jwtAuthorizationFilter; 38 | 39 | @Bean 40 | SecurityFilterChain applicationSecurity(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { 41 | 42 | JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(); 43 | jwtAuthenticationFilter.setAuthenticationManager(authenticationManager); 44 | jwtAuthenticationFilter.setFilterProcessesUrl("/login"); 45 | 46 | http 47 | .authorizeHttpRequests((authorize) -> authorize 48 | .requestMatchers("/login").permitAll() 49 | //.requestMatchers("/api/users/create").permitAll() 50 | //.requestMatchers("/api/users/all").permitAll() 51 | //.anyRequest().authenticated() 52 | .anyRequest().permitAll() 53 | ) 54 | .csrf(AbstractHttpConfigurer::disable) 55 | .addFilter(jwtAuthenticationFilter) 56 | .addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class); 57 | 58 | return http.build(); 59 | } 60 | 61 | @Bean 62 | AuthenticationManager authenticationManager() { 63 | DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); 64 | authenticationProvider.setUserDetailsService(userDetailsService); 65 | authenticationProvider.setPasswordEncoder(passwordEncoder()); 66 | 67 | return new ProviderManager(authenticationProvider); 68 | } 69 | 70 | @Bean 71 | public PasswordEncoder passwordEncoder() { 72 | return new BCryptPasswordEncoder(10); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/TokenUtils.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.JwtException; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | 9 | import java.util.Collections; 10 | import java.util.Date; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class TokenUtils { 15 | private final static String ACCESS_TOKEN_SECRET = "jMDlXW0PasDSwcnDGfaiHdr1UnVnL3gD99H+StD+kB8="; 16 | private final static Long ACCESS_TOKEN_VALIDITY_SECONDS = 100 * 60 * 60L; // 100 hours 17 | 18 | public static String createToken(String username, String email) { 19 | long expirationTime = ACCESS_TOKEN_VALIDITY_SECONDS * 1000; 20 | Date expirationDate = new Date(System.currentTimeMillis() + expirationTime); 21 | 22 | Map extra = new HashMap<>(); 23 | extra.put("username", username); 24 | 25 | return Jwts.builder() 26 | .subject(email) 27 | .expiration(expirationDate) 28 | .claims(extra) 29 | .signWith(Keys.hmacShaKeyFor(ACCESS_TOKEN_SECRET.getBytes())) 30 | .compact(); 31 | } 32 | 33 | public static UsernamePasswordAuthenticationToken getAuthentication(String token){ 34 | try { 35 | Claims claims = Jwts.parser() 36 | .setSigningKey(Keys.hmacShaKeyFor(ACCESS_TOKEN_SECRET.getBytes())) 37 | .build() 38 | .parseClaimsJws(token) 39 | .getBody(); 40 | 41 | String email = claims.getSubject(); 42 | 43 | return new UsernamePasswordAuthenticationToken(email, null, Collections.emptyList()); 44 | } catch( JwtException e){ 45 | return null; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/UserDetailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | import com.fakestore.api.persistence.entity.User; 4 | import com.fakestore.api.persistence.repository.UserRepository; 5 | import lombok.AllArgsConstructor; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | @AllArgsConstructor 13 | public class UserDetailServiceImpl implements UserDetailsService{ 14 | 15 | private final UserRepository userRepository; 16 | @Override 17 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { 18 | User user = userRepository.findOneByEmail(email) 19 | .orElseThrow(() -> new UsernameNotFoundException("User with email " + email + " not found")); 20 | 21 | return new UserDetailsImpl(user); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/security/UserDetailsImpl.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.security; 2 | 3 | import com.fakestore.api.persistence.entity.User; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | 8 | import java.util.Collection; 9 | import java.util.Collections; 10 | 11 | 12 | @AllArgsConstructor 13 | public class UserDetailsImpl implements UserDetails { 14 | 15 | private final User user; 16 | @Override 17 | public Collection getAuthorities() { 18 | return Collections.emptyList(); 19 | } 20 | 21 | @Override 22 | public String getPassword() { 23 | return user.getPassword(); 24 | } 25 | 26 | @Override 27 | public String getUsername() { 28 | return user.getEmail(); 29 | } 30 | 31 | @Override 32 | public boolean isAccountNonExpired() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public boolean isAccountNonLocked() { 38 | return true; 39 | } 40 | 41 | @Override 42 | public boolean isCredentialsNonExpired() { 43 | return true; 44 | } 45 | 46 | @Override 47 | public boolean isEnabled() { 48 | return true; 49 | } 50 | 51 | public String getFullName(){ 52 | return user.getUsername(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.service; 2 | 3 | import com.fakestore.api.dto.CategoryCreationDTO; 4 | import com.fakestore.api.dto.CategoryResponseDTO; 5 | import com.fakestore.api.exception.CategoryNotFoundException; 6 | import com.fakestore.api.persistence.entity.Category; 7 | import com.fakestore.api.persistence.repository.CategoryRepository; 8 | import lombok.AllArgsConstructor; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.Pageable; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | @AllArgsConstructor 15 | public class CategoryService { 16 | private CategoryRepository categoryRepository; 17 | public Category createCategory(CategoryCreationDTO categoryCreationDTO) { 18 | Category category = new Category(); 19 | category.setName(categoryCreationDTO.name()); 20 | category.setDescription(categoryCreationDTO.description()); 21 | categoryRepository.save(category); 22 | return category; 23 | } 24 | 25 | public Page getAllCategories(Pageable pageable) { 26 | Page categoryList = categoryRepository.findAll(pageable); 27 | return categoryList.map(this::convertToDto); 28 | } 29 | 30 | public CategoryResponseDTO convertToDto(Category category) { 31 | return new CategoryResponseDTO( 32 | category.getId(), 33 | category.getName(), 34 | category.getImage(), 35 | category.getDescription() 36 | ); 37 | } 38 | 39 | public Category updateCategory(Long id, CategoryCreationDTO categoryCreationDTO) { 40 | Category category = getCategoryById(id); 41 | category.setName(categoryCreationDTO.name()); 42 | category.setDescription(categoryCreationDTO.description()); 43 | categoryRepository.save(category); 44 | return category; 45 | } 46 | 47 | public Category getCategoryById(Long id) { 48 | return categoryRepository.findById(id).orElseThrow( 49 | () -> new CategoryNotFoundException("Category not found with ID: " + id) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/service/OrderDetailService.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.service; 2 | 3 | import com.fakestore.api.dto.OrderDetailResponseDTO; 4 | import com.fakestore.api.dto.OrderDetailUpdateDTO; 5 | import com.fakestore.api.persistence.entity.OrderDetail; 6 | import com.fakestore.api.persistence.entity.Product; 7 | import com.fakestore.api.persistence.repository.OrderDetailRepository; 8 | import com.fakestore.api.persistence.repository.ProductRepository; 9 | import lombok.AllArgsConstructor; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | 14 | @Service 15 | @AllArgsConstructor 16 | public class OrderDetailService { 17 | private final OrderDetailRepository orderDetailRepository; 18 | private final ProductRepository productRepository; 19 | private final OrderService orderService; 20 | 21 | public List getAllOrderDetailsByOrder(Long id) { 22 | List orderDetails = orderDetailRepository.findAllByOrder_Id(id); 23 | return orderDetails.stream().map(orderDetail -> new OrderDetailResponseDTO( 24 | orderDetail.getId(), 25 | orderDetail.getOrder().getId(), 26 | orderDetail.getProduct().getId(), 27 | orderDetail.getQuantity(), 28 | orderDetail.getUnitPrice() 29 | )).toList(); 30 | } 31 | 32 | public OrderDetailResponseDTO updateOrderDetail(Long id, OrderDetailUpdateDTO orderDetailUpdateDTO) { 33 | OrderDetail orderDetail = orderDetailRepository.findById(id).orElseThrow(); 34 | Product product = productRepository.findById(orderDetailUpdateDTO.productId()).orElseThrow(); 35 | orderDetail.setProduct(product); 36 | orderDetail.setQuantity(orderDetailUpdateDTO.quantity()); 37 | 38 | orderDetail.setUnitPrice(product.getPrice()); 39 | 40 | orderDetail = orderDetailRepository.save(orderDetail); 41 | 42 | orderService.updateOrderTotalPrice(orderDetail.getOrder().getId(), orderDetail.getQuantity()); 43 | 44 | return new OrderDetailResponseDTO( 45 | orderDetail.getId(), 46 | orderDetail.getOrder().getId(), 47 | orderDetail.getProduct().getId(), 48 | orderDetail.getQuantity(), 49 | orderDetail.getUnitPrice() 50 | ); 51 | } 52 | 53 | public OrderDetail getOrderDetailById(Long idOrderDetails) { 54 | return orderDetailRepository.findById(idOrderDetails).orElseThrow(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.service; 2 | 3 | import com.fakestore.api.persistence.entity.Order; 4 | import com.fakestore.api.persistence.entity.User; 5 | import com.fakestore.api.persistence.repository.OrderRepository; 6 | import lombok.AllArgsConstructor; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.Pageable; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | 13 | @Service 14 | @AllArgsConstructor 15 | public class OrderService { 16 | private final OrderRepository orderRepository; 17 | 18 | public Order saveOrder(Order newOrder) { 19 | return orderRepository.save(newOrder); 20 | } 21 | 22 | public Page getAllOrdersByUser(User user, Pageable pageable) { 23 | return orderRepository.findAllByUser(user, pageable); 24 | } 25 | 26 | public Order getOrderById(Long id) { 27 | return orderRepository.findById(id).orElseThrow(); 28 | } 29 | 30 | public void updateOrderTotalPrice(Long id, Integer quantity) { 31 | Order order = orderRepository.findById(id).orElseThrow(); 32 | 33 | double total = order.getOrderDetails().stream() 34 | .mapToDouble(detail -> detail.getUnitPrice() * detail.getQuantity()) 35 | .sum(); 36 | 37 | order.setTotal(total); 38 | 39 | orderRepository.save(order); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/service/ProductService.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.service; 2 | 3 | import com.fakestore.api.dto.ProductCreationDTO; 4 | import com.fakestore.api.dto.ProductResponseDTO; 5 | import com.fakestore.api.exception.ProductAlreadyExistsException; 6 | import com.fakestore.api.exception.ProductNotFoundException; 7 | import com.fakestore.api.persistence.entity.Category; 8 | import com.fakestore.api.persistence.entity.Product; 9 | import com.fakestore.api.persistence.repository.CategoryRepository; 10 | import com.fakestore.api.persistence.repository.ProductRepository; 11 | import jakarta.persistence.criteria.Predicate; 12 | import lombok.AllArgsConstructor; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.data.jpa.domain.Specification; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.time.LocalDate; 19 | import java.time.LocalDateTime; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.stream.Collectors; 23 | 24 | @Service 25 | @AllArgsConstructor 26 | public class ProductService { 27 | 28 | private ProductRepository productRepository; 29 | private CategoryRepository categoryRepository; 30 | 31 | 32 | public Page getAllProducts(Pageable pageable, String name, Double priceMin, Double priceMax, Long categoryId, Integer stockQuantityMin, Integer stockQuantityMax, LocalDateTime createdAtMin, LocalDateTime createdAtMax) { 33 | Specification spec = buildSpecification(name, priceMin, priceMax, categoryId, stockQuantityMin, stockQuantityMax, createdAtMin, createdAtMax); 34 | Page productList = productRepository.findAll(spec, pageable); 35 | return productList.map(this::convertToDto); 36 | 37 | //Page productList = productRepository.findAll(pageable); 38 | //return productList.map(this::convertToDto); 39 | } 40 | 41 | private Specification buildSpecification(String name, Double priceMin, Double priceMax, Long categoryId, Integer stockQuantityMin, Integer stockQuantityMax, LocalDateTime createdAtMin, LocalDateTime createdAtMax) { 42 | return (root, query, cb) -> { 43 | List predicates = new ArrayList<>(); 44 | if (name != null) { 45 | predicates.add(cb.like(cb.lower(root.get("name")), "%" + name.toLowerCase() + "%")); 46 | } 47 | if (priceMin != null) { 48 | predicates.add(cb.greaterThanOrEqualTo(root.get("price"), priceMin)); 49 | } 50 | if (priceMax != null) { 51 | predicates.add(cb.lessThanOrEqualTo(root.get("price"), priceMax)); 52 | } 53 | if (categoryId != null) { 54 | predicates.add(cb.equal(root.get("category").get("id"), categoryId)); 55 | } 56 | if (stockQuantityMin != null) { 57 | predicates.add(cb.greaterThanOrEqualTo(root.get("stockQuantity"), stockQuantityMin)); 58 | } 59 | if (stockQuantityMax != null) { 60 | predicates.add(cb.lessThanOrEqualTo(root.get("stockQuantity"), stockQuantityMax)); 61 | } 62 | if (createdAtMin != null) { 63 | predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), createdAtMin)); 64 | } 65 | if (createdAtMax != null) { 66 | predicates.add(cb.lessThanOrEqualTo(root.get("createdAt"), createdAtMax)); 67 | } 68 | return cb.and(predicates.toArray(new Predicate[0])); 69 | }; 70 | } 71 | 72 | public Product getProductById(Long id) { 73 | return productRepository.findById(id).orElseThrow( 74 | () -> new ProductNotFoundException("Product not found with ID: " + id) 75 | ); 76 | } 77 | 78 | public Product createProduct(ProductCreationDTO productCreationDTO) { 79 | validateProductDoesNotExistForName(productCreationDTO.name()); 80 | 81 | Product product = new Product(); 82 | product.setName(productCreationDTO.name()); 83 | product.setDescription(productCreationDTO.description()); 84 | product.setPrice(productCreationDTO.price()); 85 | product.setStockQuantity(productCreationDTO.stockQuantity()); 86 | Category category = categoryRepository.findById(productCreationDTO.categoryId()) 87 | .orElseThrow(); 88 | product.setCategory(category); 89 | product.setImageUrl(productCreationDTO.imageUrl()); 90 | product.setCreatedAt(LocalDate.now()); 91 | product.setUpdatedAt(LocalDate.now()); 92 | 93 | productRepository.save(product); 94 | return product; 95 | } 96 | public void validateProductDoesNotExistForName(String name){ 97 | productRepository.findByName(name) 98 | .ifPresent(product -> { 99 | throw new ProductAlreadyExistsException("Product already exists with name: " + name); 100 | }); 101 | } 102 | 103 | public ProductResponseDTO convertToDto(Product product){ 104 | return new ProductResponseDTO( 105 | product.getId(), 106 | product.getName(), 107 | product.getDescription(), 108 | product.getPrice(), 109 | product.getStockQuantity(), 110 | product.getCategory().getName(), 111 | product.getImageUrl(), 112 | product.getCreatedAt(), 113 | product.getUpdatedAt() 114 | ); 115 | } 116 | 117 | public void deleteProduct(Long id) { 118 | Product product = getProductById(id); 119 | productRepository.delete(product); 120 | } 121 | 122 | public Product updateProduct(Long id, ProductCreationDTO productCreationDTO) { 123 | Product product = getProductById(id); 124 | product.setName(productCreationDTO.name()); 125 | product.setDescription(productCreationDTO.description()); 126 | product.setPrice(productCreationDTO.price()); 127 | product.setStockQuantity(productCreationDTO.stockQuantity()); 128 | Category category = categoryRepository.findById(productCreationDTO.categoryId()) 129 | .orElseThrow(); 130 | product.setCategory(category); 131 | product.setImageUrl(productCreationDTO.imageUrl()); 132 | product.setUpdatedAt(LocalDate.now()); 133 | productRepository.save(product); 134 | return product; 135 | } 136 | 137 | public boolean existsById(Long aLong) { 138 | return productRepository.existsById(aLong); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.service; 2 | 3 | import com.fakestore.api.dto.ChangePasswordDTO; 4 | import com.fakestore.api.dto.UserCreationDTO; 5 | import com.fakestore.api.dto.UserResponseDTO; 6 | import com.fakestore.api.dto.UserUpdateDTO; 7 | import com.fakestore.api.exception.UserAlreadyExistsException; 8 | import com.fakestore.api.exception.UserNotFoundException; 9 | import com.fakestore.api.persistence.entity.User; 10 | import com.fakestore.api.persistence.repository.UserRepository; 11 | import jakarta.transaction.Transactional; 12 | import lombok.AllArgsConstructor; 13 | import org.springframework.data.domain.Page; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.security.core.Authentication; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.security.crypto.password.PasswordEncoder; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.time.LocalDate; 21 | import java.util.List; 22 | 23 | @AllArgsConstructor 24 | @Service 25 | public class UserService { 26 | private final UserRepository userRepository; 27 | private final PasswordEncoder passwordEncoder; 28 | 29 | @Transactional 30 | public User createUser(UserCreationDTO userDto) { 31 | validateUserDoesNotExistForName(userDto.username()); 32 | 33 | User user = new User(); 34 | 35 | user.setUsername(userDto.username()); 36 | // ecript password with bcrypt encoder in class SecurityConfig.java 37 | user.setPassword(passwordEncoder.encode(userDto.password())); 38 | 39 | user.setEmail(userDto.email()); 40 | user.setCreatedAt(LocalDate.now()); 41 | user.setAvatar(userDto.avatar()); 42 | user.setRole(userDto.role()); 43 | 44 | userRepository.save(user); 45 | return user; 46 | } 47 | 48 | public void validateUserDoesNotExistForName(String username){ 49 | userRepository.findByUsername(username) 50 | .ifPresent(user -> { 51 | throw new UserAlreadyExistsException("User already exists with username: " + username); 52 | }); 53 | } 54 | 55 | public Page getAllUsers(Pageable pageable) { 56 | Page userList = userRepository.findAll(pageable); 57 | return userList.map(this::convertToDto); 58 | } 59 | 60 | public UserResponseDTO convertToDto(User user) { 61 | return new UserResponseDTO( 62 | user.getId(), 63 | user.getUsername(), 64 | user.getEmail(), 65 | user.getAvatar(), 66 | user.getRole(), 67 | user.getCreatedAt() 68 | ); 69 | } 70 | 71 | public User getUserById(Long id) { 72 | return userRepository.findById(id).orElseThrow( 73 | () -> new UserNotFoundException("User not found with ID: " + id) 74 | ); 75 | } 76 | 77 | public User updateUser(Long id, UserUpdateDTO userUpdateDTO) { 78 | User user = getUserById(id); 79 | user.setUsername(userUpdateDTO.username()); 80 | user.setEmail(userUpdateDTO.email()); 81 | userRepository.save(user); 82 | return user; 83 | } 84 | 85 | public void deleteUser(Long id) { 86 | User user = getUserById(id); 87 | userRepository.delete(user); 88 | } 89 | 90 | public User getAuthenticatedUser(){ 91 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 92 | String emailAuth = authentication.getName(); 93 | return userRepository.findOneByEmail(emailAuth) 94 | .orElseThrow(() -> new UserNotFoundException("User not found with email: " + emailAuth)); 95 | } 96 | 97 | public void changePassword(Long id, ChangePasswordDTO changePasswordDTO) { 98 | User user = getUserById(id); 99 | user.setPassword(passwordEncoder.encode(changePasswordDTO.newPassword())); 100 | userRepository.save(user); 101 | } 102 | 103 | public Boolean isUsernameAvailable(String email) { 104 | return userRepository.existsByEmail(email); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/util/DateUtil.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.util; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.format.DateTimeFormatter; 5 | 6 | public class DateUtil { 7 | private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); 8 | 9 | public static LocalDateTime parseDateTime(String dateTimeStr){ 10 | if(dateTimeStr==null){ 11 | return null; 12 | } 13 | return LocalDateTime.parse(dateTimeStr, FORMATTER); 14 | } 15 | 16 | public static boolean isValidDataRange(LocalDateTime start, LocalDateTime end){ 17 | if(start==null || end==null){ 18 | return false; 19 | } 20 | return !start.isAfter(end); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/web/controller/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.web.controller; 2 | 3 | import com.fakestore.api.dto.CategoryCreationDTO; 4 | import com.fakestore.api.dto.CategoryResponseDTO; 5 | import com.fakestore.api.persistence.entity.Category; 6 | import com.fakestore.api.service.CategoryService; 7 | import lombok.AllArgsConstructor; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.data.web.PageableDefault; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | 16 | @RestController 17 | @RequestMapping("/api/categories") 18 | @AllArgsConstructor 19 | public class CategoryController { 20 | 21 | private CategoryService categoryService; 22 | 23 | @PostMapping("/create") 24 | public ResponseEntity createCategory(@RequestBody CategoryCreationDTO categoryCreationDTO){ 25 | Category createdCategory = categoryService.createCategory(categoryCreationDTO); 26 | 27 | CategoryResponseDTO categoryResponseDto = new CategoryResponseDTO( 28 | createdCategory.getId(), 29 | createdCategory.getName(), 30 | createdCategory.getImage(), 31 | createdCategory.getDescription() 32 | ); 33 | 34 | return new ResponseEntity<>(categoryResponseDto, HttpStatus.CREATED); 35 | } 36 | 37 | @GetMapping("/all") 38 | public ResponseEntity> getAllCategories(@PageableDefault(page = 0, size =5)Pageable pageable){ 39 | Page categoryList = categoryService.getAllCategories(pageable); 40 | return new ResponseEntity<>(categoryList, HttpStatus.OK); 41 | } 42 | 43 | @GetMapping("/{id}") 44 | public ResponseEntity getCategoryById(@PathVariable Long id){ 45 | Category category = categoryService.getCategoryById(id); 46 | 47 | CategoryResponseDTO categoryResponseDto = new CategoryResponseDTO( 48 | category.getId(), 49 | category.getName(), 50 | category.getImage(), 51 | category.getDescription() 52 | ); 53 | 54 | return new ResponseEntity<>(categoryResponseDto, HttpStatus.OK); 55 | } 56 | 57 | 58 | @PutMapping("/update/{id}") 59 | public ResponseEntity updateCategory(@PathVariable Long id, @RequestBody CategoryCreationDTO categoryCreationDTO){ 60 | Category updatedCategory = categoryService.updateCategory(id, categoryCreationDTO); 61 | 62 | CategoryResponseDTO categoryResponseDto = new CategoryResponseDTO( 63 | updatedCategory.getId(), 64 | updatedCategory.getName(), 65 | updatedCategory.getImage(), 66 | updatedCategory.getDescription() 67 | ); 68 | 69 | return new ResponseEntity<>(categoryResponseDto, HttpStatus.OK); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/web/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.web.controller; 2 | 3 | import com.fakestore.api.dto.OrderDTO; 4 | import com.fakestore.api.dto.OrderDetailDTO; 5 | import com.fakestore.api.dto.OrderResponseDTO; 6 | import com.fakestore.api.persistence.entity.*; 7 | import com.fakestore.api.service.OrderService; 8 | import com.fakestore.api.service.ProductService; 9 | import com.fakestore.api.service.UserService; 10 | import lombok.AllArgsConstructor; 11 | import org.springframework.data.domain.Page; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.data.web.PageableDefault; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.security.access.prepost.PreAuthorize; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.time.LocalDate; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Objects; 23 | 24 | @RestController 25 | @RequestMapping("/api/orders") 26 | @AllArgsConstructor 27 | public class OrderController { 28 | private final OrderService orderService; 29 | private final UserService userService; 30 | private final ProductService productService; 31 | 32 | @GetMapping("/all") 33 | @PreAuthorize("isAuthenticated()") 34 | public ResponseEntity> getAllOrders( 35 | @PageableDefault(page = 0, size =5)Pageable pageable 36 | ) { 37 | User user = userService.getAuthenticatedUser(); 38 | Page orders = orderService.getAllOrdersByUser(user, pageable); 39 | 40 | Page response = orders.map(order -> new OrderResponseDTO( 41 | order.getId(), 42 | order.getOrderDate().toString(), 43 | order.getOrderStatus().toString(), 44 | order.getTotal() 45 | )); 46 | 47 | return ResponseEntity.ok(response); 48 | } 49 | 50 | @PostMapping("/create") 51 | @PreAuthorize("isAuthenticated()") 52 | public ResponseEntity createOrder(@RequestBody OrderDTO orderDTO) { 53 | User user = userService.getAuthenticatedUser(); 54 | 55 | Order order = new Order(); 56 | order.setUser(user); 57 | order.setOrderDate(LocalDate.now()); 58 | order.setOrderStatus(OrderStatus.PENDING); 59 | order.setOrderDetails(new ArrayList<>()); 60 | 61 | for (OrderDetailDTO detailDTO : orderDTO.orderDetails()) { 62 | Product product = productService.getProductById(detailDTO.id()); 63 | 64 | OrderDetail detail = new OrderDetail(); 65 | detail.setOrder(order); 66 | detail.setProduct(product); 67 | detail.setQuantity(detailDTO.quantity()); 68 | detail.setUnitPrice(product.getPrice()); 69 | 70 | order.getOrderDetails().add(detail); 71 | } 72 | 73 | double total = order.getOrderDetails().stream() 74 | .mapToDouble(detail -> detail.getUnitPrice() * detail.getQuantity()) 75 | .sum(); 76 | order.setTotal(total); 77 | 78 | Order savedOrder = orderService.saveOrder(order); 79 | return ResponseEntity.status(HttpStatus.CREATED).body(new OrderResponseDTO( 80 | savedOrder.getId(), 81 | savedOrder.getOrderDate().toString(), 82 | savedOrder.getOrderStatus().toString(), 83 | savedOrder.getTotal() 84 | )); 85 | } 86 | 87 | @GetMapping("/{id}") 88 | @PreAuthorize("isAuthenticated()") 89 | public ResponseEntity getOrderById(@PathVariable Long id) { 90 | User user = userService.getAuthenticatedUser(); 91 | Order order = orderService.getOrderById(id); 92 | 93 | System.out.println("order.getUser().getId(): " + order.getUser().getId()); 94 | System.out.println("user.getId(): " + user.getId()); 95 | 96 | if (!Objects.equals(order.getUser().getId(), user.getId())) { 97 | return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); 98 | } 99 | 100 | return ResponseEntity.ok(new OrderResponseDTO( 101 | order.getId(), 102 | order.getOrderDate().toString(), 103 | order.getOrderStatus().toString(), 104 | order.getTotal() 105 | )); 106 | } 107 | 108 | @PutMapping("/cancel/{id}") 109 | @PreAuthorize(("isAuthenticated()")) 110 | public ResponseEntity cancelOrder(@PathVariable Long id) { 111 | User user = userService.getAuthenticatedUser(); 112 | Order order = orderService.getOrderById(id); 113 | 114 | if (!Objects.equals(order.getUser().getId(), user.getId())) { 115 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 116 | } 117 | 118 | order.setOrderStatus(OrderStatus.CANCELLED); 119 | Order savedOrder = orderService.saveOrder(order); 120 | return ResponseEntity.ok(new OrderResponseDTO( 121 | savedOrder.getId(), 122 | savedOrder.getOrderDate().toString(), 123 | savedOrder.getOrderStatus().toString(), 124 | savedOrder.getTotal() 125 | )); 126 | } 127 | 128 | @PutMapping("/retake/{id}") 129 | @PreAuthorize(("isAuthenticated()")) 130 | public ResponseEntity retakeOrder(@PathVariable Long id) { 131 | User user = userService.getAuthenticatedUser(); 132 | Order order = orderService.getOrderById(id); 133 | 134 | if (!Objects.equals(order.getUser().getId(), user.getId())) { 135 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 136 | } 137 | 138 | order.setOrderStatus(OrderStatus.PENDING); 139 | Order savedOrder = orderService.saveOrder(order); 140 | return ResponseEntity.ok(new OrderResponseDTO( 141 | savedOrder.getId(), 142 | savedOrder.getOrderDate().toString(), 143 | savedOrder.getOrderStatus().toString(), 144 | savedOrder.getTotal() 145 | )); 146 | } 147 | 148 | @PutMapping("/complete/{id}") 149 | @PreAuthorize(("isAuthenticated()")) 150 | public ResponseEntity completeOrder(@PathVariable Long id) { 151 | User user = userService.getAuthenticatedUser(); 152 | Order order = orderService.getOrderById(id); 153 | 154 | if (!Objects.equals(order.getUser().getId(), user.getId())) { 155 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); 156 | } 157 | 158 | order.setOrderStatus(OrderStatus.COMPLETED); 159 | Order savedOrder = orderService.saveOrder(order); 160 | return ResponseEntity.ok(new OrderResponseDTO( 161 | savedOrder.getId(), 162 | savedOrder.getOrderDate().toString(), 163 | savedOrder.getOrderStatus().toString(), 164 | savedOrder.getTotal() 165 | )); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/web/controller/OrderDetailController.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.web.controller; 2 | 3 | import com.fakestore.api.dto.OrderDetailResponseDTO; 4 | import com.fakestore.api.dto.OrderDetailUpdateDTO; 5 | import com.fakestore.api.persistence.entity.OrderDetail; 6 | import com.fakestore.api.persistence.entity.User; 7 | import com.fakestore.api.service.OrderDetailService; 8 | import com.fakestore.api.service.ProductService; 9 | import com.fakestore.api.service.UserService; 10 | import lombok.AllArgsConstructor; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | @RestController 20 | @RequestMapping("/api/order-details") 21 | @AllArgsConstructor 22 | public class OrderDetailController { 23 | private final OrderDetailService orderDetailService; 24 | private final UserService userService; 25 | private final ProductService productService; 26 | 27 | // GET /api/order-details 28 | // GET /api/order-details/all/{id} -> id es el is del Order al que pertenece el OrderDetail 29 | @GetMapping("/all/{id}") // id es el id del Order al que pertenece el OrderDetail 30 | public ResponseEntity> getAllOrderDetails(@PathVariable Long id) { 31 | List orderDetails = orderDetailService.getAllOrderDetailsByOrder(id); 32 | return new ResponseEntity<>(orderDetails, HttpStatus.OK); 33 | } 34 | 35 | // POST /api/order-details 36 | // PUT /api/order-details/{id} // solo el usuario que hizo la orden puede modificarla 37 | @PutMapping("/{idOrderDetails}") 38 | @PreAuthorize("isAuthenticated()") 39 | public ResponseEntity updateOrderDetail(@PathVariable Long idOrderDetails, @RequestBody OrderDetailUpdateDTO orderDetailUpdateDTO) { 40 | User user = userService.getAuthenticatedUser(); 41 | 42 | OrderDetail orderDetail = orderDetailService.getOrderDetailById(idOrderDetails); 43 | User userOrder = orderDetail.getOrder().getUser(); 44 | 45 | if (!Objects.equals(user.getId(), userOrder.getId())) { 46 | return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); 47 | } 48 | 49 | // verificamos que el producto exista, y que la cantidad sea mayor a 0 50 | if (orderDetailUpdateDTO.productId() == null || orderDetailUpdateDTO.quantity() == null || orderDetailUpdateDTO.quantity() <= 0) { 51 | // TODO: mejorar el mensaje de error, para que sea mas especifico (se puede hacer con un DTO, o con un mensaje de error personalizado) 52 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 53 | } 54 | 55 | // verificamos que el producto exista en la base de datos 56 | if (!productService.existsById(orderDetailUpdateDTO.productId())) { 57 | // TODO: mejorar el mensaje de error, para que sea mas especifico (se puede hacer con un DTO, o con un mensaje de error personalizado) 58 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 59 | } 60 | 61 | OrderDetailResponseDTO orderDetailDTO = orderDetailService.updateOrderDetail(idOrderDetails, orderDetailUpdateDTO); 62 | return new ResponseEntity<>(orderDetailDTO, HttpStatus.OK); 63 | } 64 | // DELETE /api/order-details/{id} // solo administradores 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/web/controller/ProductController.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.web.controller; 2 | 3 | 4 | import com.fakestore.api.dto.ProductCreationDTO; 5 | import com.fakestore.api.dto.ProductResponseDTO; 6 | import com.fakestore.api.persistence.entity.Product; 7 | import com.fakestore.api.service.ProductService; 8 | import com.fakestore.api.util.DateUtil; 9 | import jakarta.validation.constraints.Min; 10 | import jakarta.validation.constraints.Size; 11 | import lombok.AllArgsConstructor; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.data.web.PageableDefault; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.web.bind.annotation.*; 18 | 19 | import java.time.DateTimeException; 20 | import java.time.LocalDateTime; 21 | import java.util.List; 22 | 23 | @RestController 24 | @RequestMapping("/api/products") 25 | @AllArgsConstructor 26 | public class ProductController { 27 | 28 | private ProductService productService; 29 | 30 | @GetMapping("/all") 31 | public ResponseEntity> getAllProducts( 32 | @PageableDefault(page =0, size =5) Pageable pageable, 33 | @RequestParam(required = false) @Size(max = 255) String name, 34 | @RequestParam(required = false) @Min(0) Double priceMin, 35 | @RequestParam(required = false) @Min(0) Double priceMax, 36 | @RequestParam(required = false) Long categoryId, 37 | @RequestParam(required = false) @Min(0) Integer stockQuantityMin, 38 | @RequestParam(required = false) @Min(0) Integer stockQuantityMax, 39 | @RequestParam(required = false) String createdAtMin, 40 | @RequestParam(required = false) String createdAtMax 41 | ){ 42 | 43 | if (priceMin != null && priceMax != null) { 44 | if (priceMin > priceMax) { 45 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 46 | } 47 | } 48 | 49 | if (stockQuantityMin != null && stockQuantityMax != null) { 50 | if (stockQuantityMin > stockQuantityMax) { 51 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 52 | } 53 | } 54 | 55 | LocalDateTime minDate = null, maxDate = null; 56 | 57 | if (createdAtMin != null && !createdAtMin.isEmpty()) { 58 | try { 59 | minDate = DateUtil.parseDateTime(createdAtMin); 60 | } catch (DateTimeException e) { 61 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 62 | } 63 | } 64 | 65 | if (createdAtMax != null && !createdAtMax.isEmpty()) { 66 | try { 67 | maxDate = DateUtil.parseDateTime(createdAtMax); 68 | } catch (DateTimeException e) { 69 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 70 | } 71 | } 72 | 73 | if (minDate != null && maxDate != null && !DateUtil.isValidDataRange(minDate, maxDate)) { 74 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 75 | } 76 | 77 | Page productList = productService.getAllProducts(pageable, name, priceMin, priceMax, categoryId, stockQuantityMin, stockQuantityMax, minDate, maxDate); 78 | return new ResponseEntity<>(productList, HttpStatus.OK); 79 | } 80 | 81 | @GetMapping("/{id}") 82 | public ResponseEntity getProductById(@PathVariable Long id){ 83 | Product product = productService.getProductById(id); 84 | 85 | ProductResponseDTO productResponseDto = new ProductResponseDTO( 86 | product.getId(), 87 | product.getName(), 88 | product.getDescription(), 89 | product.getPrice(), 90 | product.getStockQuantity(), 91 | product.getCategory().getName(), 92 | product.getImageUrl(), 93 | product.getCreatedAt(), 94 | product.getUpdatedAt() 95 | ); 96 | 97 | return new ResponseEntity<>(productResponseDto, HttpStatus.OK); 98 | } 99 | 100 | @PostMapping("/create") 101 | public ResponseEntity createProduct(@RequestBody ProductCreationDTO productCreationDTO){ 102 | Product createdProduct = productService.createProduct(productCreationDTO); 103 | 104 | ProductResponseDTO productResponseDto = new ProductResponseDTO( 105 | createdProduct.getId(), 106 | createdProduct.getName(), 107 | createdProduct.getDescription(), 108 | createdProduct.getPrice(), 109 | createdProduct.getStockQuantity(), 110 | createdProduct.getCategory().getName(), 111 | createdProduct.getImageUrl(), 112 | createdProduct.getCreatedAt(), 113 | createdProduct.getUpdatedAt() 114 | ); 115 | 116 | return new ResponseEntity<>(productResponseDto, HttpStatus.CREATED); 117 | } 118 | 119 | @PutMapping("/update/{id}") 120 | public ResponseEntity updateProduct(@PathVariable Long id, @RequestBody ProductCreationDTO productCreationDTO){ 121 | Product updatedProduct = productService.updateProduct(id, productCreationDTO); 122 | 123 | ProductResponseDTO productResponseDto = new ProductResponseDTO( 124 | updatedProduct.getId(), 125 | updatedProduct.getName(), 126 | updatedProduct.getDescription(), 127 | updatedProduct.getPrice(), 128 | updatedProduct.getStockQuantity(), 129 | updatedProduct.getCategory().getName(), 130 | updatedProduct.getImageUrl(), 131 | updatedProduct.getCreatedAt(), 132 | updatedProduct.getUpdatedAt() 133 | ); 134 | 135 | return new ResponseEntity<>(productResponseDto, HttpStatus.OK); 136 | } 137 | 138 | @DeleteMapping("/delete/{id}") 139 | public ResponseEntity deleteProduct(@PathVariable Long id){ 140 | productService.deleteProduct(id); 141 | return new ResponseEntity<>(HttpStatus.OK); 142 | } 143 | } 144 | 145 | -------------------------------------------------------------------------------- /src/main/java/com/fakestore/api/web/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api.web.controller; 2 | 3 | import com.fakestore.api.dto.*; 4 | import com.fakestore.api.persistence.entity.User; 5 | import com.fakestore.api.service.UserService; 6 | import jakarta.validation.Valid; 7 | import lombok.AllArgsConstructor; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.data.web.PageableDefault; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.http.ResponseEntity; 13 | import org.springframework.security.access.prepost.PreAuthorize; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import java.util.Objects; 17 | 18 | @RestController 19 | @RequestMapping("/api/users") 20 | @AllArgsConstructor 21 | public class UserController { 22 | 23 | private final UserService userService; 24 | 25 | @GetMapping("/all") 26 | public ResponseEntity> getAllUsers( 27 | @PageableDefault(size = 5) Pageable pageable 28 | ){ 29 | Page userList = userService.getAllUsers(pageable); 30 | return new ResponseEntity<>(userList, HttpStatus.OK); 31 | } 32 | 33 | @GetMapping("/{id}") 34 | public ResponseEntity getUserById(@PathVariable Long id){ 35 | 36 | User createdUser = userService.getUserById(id); 37 | UserResponseDTO userResponseDTO = new UserResponseDTO( 38 | createdUser.getId(), 39 | createdUser.getUsername(), 40 | createdUser.getEmail(), 41 | createdUser.getAvatar(), 42 | createdUser.getRole(), 43 | createdUser.getCreatedAt() 44 | ); 45 | 46 | return new ResponseEntity<>(userResponseDTO, HttpStatus.OK); 47 | } 48 | 49 | @PostMapping("/create") 50 | public ResponseEntity createUser(@RequestBody UserCreationDTO userCreationDTO){ 51 | User createdUser = userService.createUser(userCreationDTO); 52 | UserResponseDTO userResponseDTO = new UserResponseDTO( 53 | createdUser.getId(), 54 | createdUser.getUsername(), 55 | createdUser.getEmail(), 56 | createdUser.getAvatar(), 57 | createdUser.getRole(), 58 | createdUser.getCreatedAt() 59 | ); 60 | return new ResponseEntity<>(userResponseDTO, HttpStatus.CREATED); 61 | } 62 | 63 | @PutMapping("/update/{id}") 64 | @PreAuthorize("isAuthenticated()") 65 | public ResponseEntity updateUser(@PathVariable Long id, @RequestBody UserUpdateDTO userUpdateDto){ 66 | User user = userService.getAuthenticatedUser(); 67 | 68 | if(!Objects.equals(user.getId(), id)){ 69 | return new ResponseEntity<>(HttpStatus.FORBIDDEN); 70 | } 71 | User updatedUser = userService.updateUser(id, userUpdateDto); 72 | UserResponseDTO userResponseDTO = new UserResponseDTO( 73 | updatedUser.getId(), 74 | updatedUser.getUsername(), 75 | updatedUser.getEmail(), 76 | updatedUser.getAvatar(), 77 | updatedUser.getRole(), 78 | updatedUser.getCreatedAt() 79 | ); 80 | return new ResponseEntity<>(userResponseDTO, HttpStatus.OK); 81 | } 82 | 83 | @DeleteMapping("/delete/{id}") 84 | public ResponseEntity deleteUser(@PathVariable Long id){ 85 | userService.deleteUser(id); 86 | return new ResponseEntity<>(HttpStatus.NO_CONTENT); 87 | } 88 | 89 | @PostMapping("{id}/change-password") 90 | @PreAuthorize("isAuthenticated()") 91 | public ResponseEntity changePassword(@PathVariable Long id, @Valid @RequestBody ChangePasswordDTO changePasswordDTO){ 92 | User user = userService.getAuthenticatedUser(); 93 | if(!Objects.equals(user.getId(), id)){ 94 | return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); 95 | } 96 | userService.changePassword(id, changePasswordDTO); 97 | return new ResponseEntity<>(HttpStatus.NO_CONTENT); 98 | } 99 | 100 | @PostMapping("/is-available") 101 | public ResponseEntity isUsernameAvailable(@RequestBody emailDTO emailDTO){ 102 | Boolean isAvailable = userService.isUsernameAvailable(emailDTO.email()); 103 | return new ResponseEntity<>(isAvailable, HttpStatus.OK); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/resources/.gcloudignore: -------------------------------------------------------------------------------- 1 | .mvn 2 | .target 3 | mvnw 4 | mvnw.cmd -------------------------------------------------------------------------------- /src/main/resources/db/migration/V1__initial_database.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users( 2 | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 | username VARCHAR(255) NOT NULL, 4 | password VARCHAR(255) NOT NULL, 5 | email VARCHAR(255) NOT NULL, 6 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 7 | ); 8 | 9 | CREATE TABLE categories( 10 | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, 11 | name VARCHAR(255) NOT NULL, 12 | description VARCHAR(255) NOT NULL 13 | ); 14 | 15 | CREATE TABLE products( 16 | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, 17 | name VARCHAR(255) NOT NULL, 18 | description TEXT, 19 | price DECIMAL(10,2) NOT NULL, 20 | stock_quantity INTEGER NOT NULL, 21 | image_url VARCHAR(255), 22 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP, 23 | updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 24 | category_id BIGINT NOT NULL, 25 | FOREIGN KEY(category_id) REFERENCES categories(id) 26 | ); 27 | 28 | CREATE TABLE orders( 29 | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, 30 | order_date DATETIME DEFAULT CURRENT_TIMESTAMP, 31 | order_status VARCHAR(255) NOT NULL, 32 | total DECIMAL(10,2) NOT NULL, 33 | user_id BIGINT NOT NULL, 34 | FOREIGN KEY(user_id) REFERENCES users(id) 35 | ); 36 | 37 | CREATE TABLE order_details( 38 | id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, 39 | quantity INTEGER NOT NULL, 40 | unit_price DECIMAL(10,2) NOT NULL, 41 | order_id BIGINT NOT NULL, 42 | product_id BIGINT NOT NULL, 43 | FOREIGN KEY(order_id) REFERENCES orders(id), 44 | FOREIGN KEY(product_id) REFERENCES products(id) 45 | ); 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V2__Add_unique_constraint_to_email.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD UNIQUE (email); 2 | -------------------------------------------------------------------------------- /src/main/resources/db/migration/V3__Add_Image_To_Categories.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE categories ADD COLUMN image VARCHAR(255); -------------------------------------------------------------------------------- /src/main/resources/db/migration/V4__Add_Avatar_And_Role_To_Users.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE users ADD COLUMN avatar VARCHAR(255); 2 | ALTER TABLE users ADD COLUMN role ENUM('ADMIN', 'CUSTOMER') NOT NULL DEFAULT 'CUSTOMER'; 3 | -------------------------------------------------------------------------------- /src/test/java/com/fakestore/api/FakeStoreApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.fakestore.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class FakeStoreApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------