├── .idea ├── .gitignore ├── compiler.xml ├── encodings.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── README.md ├── Run.md ├── mvnw ├── mvnw.cmd ├── myblog-ddl-script.sql ├── pdfs.zip ├── pom.xml ├── springboot-blog-rest-api.iml └── src ├── main ├── java │ └── com │ │ └── springboot │ │ └── blog │ │ ├── SpringbootBlogRestApiApplication.java │ │ ├── config │ │ └── SecurityConfig.java │ │ ├── controller │ │ ├── AuthController.java │ │ ├── CategoryController.java │ │ ├── CommentController.java │ │ └── PostController.java │ │ ├── entity │ │ ├── Category.java │ │ ├── Comment.java │ │ ├── Post.java │ │ ├── Role.java │ │ └── User.java │ │ ├── exception │ │ ├── BlogAPIException.java │ │ ├── GlobalExceptionHandler.java │ │ └── ResourceNotFoundException.java │ │ ├── payload │ │ ├── CategoryDto.java │ │ ├── CommentDto.java │ │ ├── ErrorDetails.java │ │ ├── JWTAuthResponse.java │ │ ├── LoginDto.java │ │ ├── PostDto.java │ │ ├── PostResponse.java │ │ └── RegisterDto.java │ │ ├── repository │ │ ├── CategoryRepository.java │ │ ├── CommentRepository.java │ │ ├── PostRepository.java │ │ ├── RoleRepository.java │ │ └── UserRepository.java │ │ ├── security │ │ ├── CustomUserDetailsService.java │ │ ├── JwtAuthenticationEntryPoint.java │ │ ├── JwtAuthenticationFilter.java │ │ └── JwtTokenProvider.java │ │ ├── service │ │ ├── AuthService.java │ │ ├── CategoryService.java │ │ ├── CommentService.java │ │ ├── PostService.java │ │ └── impl │ │ │ ├── AuthServiceImpl.java │ │ │ ├── CategoryServiceImpl.java │ │ │ ├── CommentServiceImpl.java │ │ │ └── PostServiceImpl.java │ │ └── utils │ │ ├── AppConstants.java │ │ └── PasswordGeneratorEncoder.java └── resources │ ├── application-dev.properties │ ├── application-prod.properties │ ├── application-qa.properties │ └── application.properties └── test └── java └── com └── springboot └── blog └── SpringbootBlogRestApiApplicationTests.java /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The source code of this project is upgraded to Spring Boot 3.2.1 and jwt 0.12.3 2 | 3 | # Source code of Bestseller Udemy Course - Building Real-Time REST APIs with Spring Boot 4 | Learn how to build real-time REST APIs with Spring Boot by building a complete Blog App. 5 | 6 | This course supports Spring Boot 3+, Spring Security 6+ 7 | 8 | Reach out to me for a discount coupon at javaguides.net@gmail.com 9 | 10 | Udemy Course Link - [Building Real-Time REST APIs with Spring Boot - Blog App](https://www.udemy.com/course/building-real-time-rest-apis-with-spring-boot/?referralCode=6312172DF8B8C2C11F5E) 11 | 12 | # What you'll learn in this course? 13 | - Learn how to build great REST APIs for Blog App using Spring Boot, Spring Security, JWT, Spring Data JPA (Hibernate), MySQL database 14 | - Learn REST basics - Resource, Sub-resource, URI, HTTP methods, HTTP status codes 15 | - Learn how to build CRUD REST APIs 16 | - Learn how to build REST APIs for Pagination and Sorting 17 | - Learn how to build REST APIs for Login/Signin and Signup 18 | - Learn how to use Lombok 19 | - Learn how to use DTOs 20 | - Learn Spring boot REST API exception handling 21 | - Learn Spring boot REST API validation 22 | - Learn how to use Spring security in the Spring boot project 23 | - Learn Spring Security In-memory and DB authentication and authorization 24 | - Learn how to secure REST APIs (role-based security) 25 | - Learn how to write query methods using Spring Data JPA 26 | - Learn one-to-many and many-to-many JPA mappings 27 | - Learn how to test REST APIs using Postman REST Client 28 | - Learn What is JWT and How it Works 29 | - Learn How to configure JWT ( JSON Web Token) in Spring Security 30 | - Learn how to secure REST APIs using JWT 31 | - Learn how to use JWT with Login API 32 | - Learn versioning REST APIs 33 | - Learn Important 4 versioning REST API strategies 34 | - Deploy Spring Boot Blog App on AWS Cloud 35 | - Learn Docker 36 | - Learn Spring and Spring Boot Annotations 37 | - Interview QA 38 | -------------------------------------------------------------------------------- /Run.md: -------------------------------------------------------------------------------- 1 | # Steps to Run Spring Boot Blog App 2 | ## 1. Maven Build the Project 3 | If you have installed Maven on your machine then use the below command: 4 | ``` 5 | mvn clean package 6 | ``` 7 | If you haven't insatlled Maven on your machine then use below command: 8 | ``` 9 | ./mvnw clean package 10 | ``` 11 | Note: Go to root directory of the project and execute above command. 12 | ## 2. Create a Database 13 | Before running Spring boot blog application, you need to create the MySQL database. 14 | 15 | Use the below SQL database to create the MySQL database: 16 | ```sql 17 | create database myblog 18 | ``` 19 | Database name - myblog 20 | ## 3. Run Spring Boot Project 21 | Use below command to run Spring boot application: 22 | ``` 23 | mvn spring-boot:run 24 | ``` 25 | Once you run Spring boot application, Hibernate will create the database tables autimatically. 26 | However, you can refer to DDL scritp for all tables here: 27 | https://github.com/RameshMF/springboot-blog-rest-api/blob/main/myblog-ddl-script.sql 28 | ## 4. Insert Data 29 | User below Insert SQL statements to insert records into roles table: 30 | ```sql 31 | INSERT INTO `myblog.roles` VALUES (1,'ROLE_ADMIN'),(2,'ROLE_USER'); 32 | ``` 33 | Now, Spring boot blog application is ready to use. 34 | -------------------------------------------------------------------------------- /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 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /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 Maven Start Up Batch script 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 M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /myblog-ddl-script.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `posts`; 2 | /*!40101 SET @saved_cs_client = @@character_set_client */; 3 | /*!50503 SET character_set_client = utf8mb4 */; 4 | CREATE TABLE `posts` ( 5 | `id` bigint NOT NULL AUTO_INCREMENT, 6 | `content` varchar(255) NOT NULL, 7 | `description` varchar(255) NOT NULL, 8 | `title` varchar(255) NOT NULL, 9 | PRIMARY KEY (`id`), 10 | UNIQUE KEY `UKmchce1gm7f6otpphxd6ixsdps` (`title`) 11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 12 | 13 | DROP TABLE IF EXISTS `comments`; 14 | /*!40101 SET @saved_cs_client = @@character_set_client */; 15 | /*!50503 SET character_set_client = utf8mb4 */; 16 | CREATE TABLE `comments` ( 17 | `id` bigint NOT NULL AUTO_INCREMENT, 18 | `body` varchar(255) DEFAULT NULL, 19 | `email` varchar(255) DEFAULT NULL, 20 | `name` varchar(255) DEFAULT NULL, 21 | `post_id` bigint NOT NULL, 22 | PRIMARY KEY (`id`), 23 | KEY `FKh4c7lvsc298whoyd4w9ta25cr` (`post_id`), 24 | CONSTRAINT `FKh4c7lvsc298whoyd4w9ta25cr` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 26 | 27 | DROP TABLE IF EXISTS `users`; 28 | /*!40101 SET @saved_cs_client = @@character_set_client */; 29 | /*!50503 SET character_set_client = utf8mb4 */; 30 | CREATE TABLE `users` ( 31 | `id` bigint NOT NULL AUTO_INCREMENT, 32 | `email` varchar(255) DEFAULT NULL, 33 | `name` varchar(255) DEFAULT NULL, 34 | `password` varchar(255) DEFAULT NULL, 35 | `username` varchar(255) DEFAULT NULL, 36 | PRIMARY KEY (`id`), 37 | UNIQUE KEY `UKr43af9ap4edm43mmtq01oddj6` (`username`), 38 | UNIQUE KEY `UK6dotkott2kjsp8vw4d0m25fb7` (`email`) 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 40 | 41 | DROP TABLE IF EXISTS `roles`; 42 | /*!40101 SET @saved_cs_client = @@character_set_client */; 43 | /*!50503 SET character_set_client = utf8mb4 */; 44 | CREATE TABLE `roles` ( 45 | `id` bigint NOT NULL AUTO_INCREMENT, 46 | `name` varchar(60) DEFAULT NULL, 47 | PRIMARY KEY (`id`) 48 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 49 | 50 | DROP TABLE IF EXISTS `user_roles`; 51 | /*!40101 SET @saved_cs_client = @@character_set_client */; 52 | /*!50503 SET character_set_client = utf8mb4 */; 53 | CREATE TABLE `user_roles` ( 54 | `user_id` bigint NOT NULL, 55 | `role_id` bigint NOT NULL, 56 | PRIMARY KEY (`user_id`,`role_id`), 57 | KEY `FKh8ciramu9cc9q3qcqiv4ue8a6` (`role_id`), 58 | CONSTRAINT `FKh8ciramu9cc9q3qcqiv4ue8a6` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`), 59 | CONSTRAINT `FKhfh9dx7w3ubf1co1vdev94g3f` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) 60 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -------------------------------------------------------------------------------- /pdfs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RameshMF/springboot-blog-rest-api/b08023289b57179cba12d3a0c09e5a12b7350493/pdfs.zip -------------------------------------------------------------------------------- /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.springboot.blog 12 | springboot-blog-rest-api 13 | 0.0.1-SNAPSHOT 14 | springboot-blog-rest-api 15 | Spring boot blog application rest api's 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-web 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-devtools 32 | runtime 33 | true 34 | 35 | 36 | com.mysql 37 | mysql-connector-j 38 | runtime 39 | 40 | 41 | org.projectlombok 42 | lombok 43 | true 44 | 45 | 46 | 47 | org.modelmapper 48 | modelmapper 49 | 2.3.9 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-validation 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-test 59 | test 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-security 64 | 65 | 66 | 67 | io.jsonwebtoken 68 | jjwt-impl 69 | 0.12.3 70 | runtime 71 | 72 | 73 | 74 | io.jsonwebtoken 75 | jjwt-api 76 | 0.12.3 77 | 78 | 79 | 80 | io.jsonwebtoken 81 | jjwt-jackson 82 | 0.12.3 83 | runtime 84 | 85 | 86 | 87 | org.springdoc 88 | springdoc-openapi-starter-webmvc-ui 89 | 2.3.0 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | org.springframework.boot 98 | spring-boot-maven-plugin 99 | 100 | 101 | 102 | org.projectlombok 103 | lombok 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /springboot-blog-rest-api.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/SpringbootBlogRestApiApplication.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | @SpringBootApplication 9 | public class SpringbootBlogRestApiApplication { 10 | 11 | @Bean 12 | public ModelMapper modelMapper(){ 13 | return new ModelMapper(); 14 | } 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(SpringbootBlogRestApiApplication.class, args); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.config; 2 | 3 | import com.springboot.blog.security.JwtAuthenticationEntryPoint; 4 | import com.springboot.blog.security.JwtAuthenticationFilter; 5 | import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; 6 | import io.swagger.v3.oas.annotations.security.SecurityScheme; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.HttpMethod; 10 | import org.springframework.security.authentication.AuthenticationManager; 11 | import org.springframework.security.config.Customizer; 12 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 13 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 14 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 15 | import org.springframework.security.config.http.SessionCreationPolicy; 16 | import org.springframework.security.core.userdetails.User; 17 | import org.springframework.security.core.userdetails.UserDetails; 18 | import org.springframework.security.core.userdetails.UserDetailsService; 19 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 20 | import org.springframework.security.crypto.password.PasswordEncoder; 21 | import org.springframework.security.provisioning.InMemoryUserDetailsManager; 22 | import org.springframework.security.web.SecurityFilterChain; 23 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 24 | 25 | @Configuration 26 | @EnableMethodSecurity 27 | @SecurityScheme( 28 | name = "Bear Authentication", 29 | type = SecuritySchemeType.HTTP, 30 | bearerFormat = "JWT", 31 | scheme = "bearer" 32 | ) 33 | public class SecurityConfig { 34 | 35 | private UserDetailsService userDetailsService; 36 | 37 | private JwtAuthenticationEntryPoint authenticationEntryPoint; 38 | 39 | private JwtAuthenticationFilter authenticationFilter; 40 | 41 | public SecurityConfig(UserDetailsService userDetailsService, 42 | JwtAuthenticationEntryPoint authenticationEntryPoint, 43 | JwtAuthenticationFilter authenticationFilter){ 44 | this.userDetailsService = userDetailsService; 45 | this.authenticationEntryPoint = authenticationEntryPoint; 46 | this.authenticationFilter = authenticationFilter; 47 | } 48 | 49 | @Bean 50 | public static PasswordEncoder passwordEncoder(){ 51 | return new BCryptPasswordEncoder(); 52 | } 53 | 54 | @Bean 55 | public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { 56 | return configuration.getAuthenticationManager(); 57 | } 58 | 59 | @Bean 60 | SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 61 | 62 | http.csrf(csrf -> csrf.disable()) 63 | .authorizeHttpRequests((authorize) -> 64 | //authorize.anyRequest().authenticated() 65 | authorize.requestMatchers(HttpMethod.GET, "/api/**").permitAll() 66 | //.requestMatchers(HttpMethod.GET, "/api/categories/**").permitAll() 67 | .requestMatchers("/api/auth/**").permitAll() 68 | .requestMatchers("/swagger-ui/**").permitAll() 69 | .requestMatchers("/v3/api-docs/**").permitAll() 70 | .anyRequest().authenticated() 71 | 72 | ).exceptionHandling( exception -> exception 73 | .authenticationEntryPoint(authenticationEntryPoint) 74 | ).sessionManagement( session -> session 75 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 76 | ); 77 | 78 | http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class); 79 | 80 | return http.build(); 81 | } 82 | 83 | // @Bean 84 | // public UserDetailsService userDetailsService(){ 85 | // UserDetails ramesh = User.builder() 86 | // .username("ramesh") 87 | // .password(passwordEncoder().encode("ramesh")) 88 | // .roles("USER") 89 | // .build(); 90 | // 91 | // UserDetails admin = User.builder() 92 | // .username("admin") 93 | // .password(passwordEncoder().encode("admin")) 94 | // .roles("ADMIN") 95 | // .build(); 96 | // return new InMemoryUserDetailsManager(ramesh, admin); 97 | // } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/controller/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.controller; 2 | 3 | import com.springboot.blog.payload.JWTAuthResponse; 4 | import com.springboot.blog.payload.LoginDto; 5 | import com.springboot.blog.payload.RegisterDto; 6 | import com.springboot.blog.service.AuthService; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | @RequestMapping("/api/auth") 16 | public class AuthController { 17 | 18 | private AuthService authService; 19 | 20 | public AuthController(AuthService authService) { 21 | this.authService = authService; 22 | } 23 | 24 | // Build Login REST API 25 | @PostMapping(value = {"/login", "/signin"}) 26 | public ResponseEntity login(@RequestBody LoginDto loginDto){ 27 | String token = authService.login(loginDto); 28 | 29 | JWTAuthResponse jwtAuthResponse = new JWTAuthResponse(); 30 | jwtAuthResponse.setAccessToken(token); 31 | 32 | return ResponseEntity.ok(jwtAuthResponse); 33 | } 34 | 35 | // Build Register REST API 36 | @PostMapping(value = {"/register", "/signup"}) 37 | public ResponseEntity register(@RequestBody RegisterDto registerDto){ 38 | String response = authService.register(registerDto); 39 | return new ResponseEntity<>(response, HttpStatus.CREATED); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/controller/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.controller; 2 | 3 | import com.springboot.blog.entity.Category; 4 | import com.springboot.blog.payload.CategoryDto; 5 | import com.springboot.blog.service.CategoryService; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.access.prepost.PreAuthorize; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/api/v1/categories") 15 | public class CategoryController { 16 | 17 | private CategoryService categoryService; 18 | 19 | public CategoryController(CategoryService categoryService) { 20 | this.categoryService = categoryService; 21 | } 22 | 23 | // Build Add Category REST API 24 | @PostMapping 25 | @PreAuthorize("hasRole('ADMIN')") 26 | public ResponseEntity addCategory(@RequestBody CategoryDto categoryDto){ 27 | CategoryDto savedCategory = categoryService.addCategory(categoryDto); 28 | return new ResponseEntity<>(savedCategory, HttpStatus.CREATED); 29 | } 30 | 31 | // Build Get Category REST API 32 | @GetMapping("{id}") 33 | public ResponseEntity getCategory(@PathVariable("id") Long categoryId){ 34 | CategoryDto categoryDto = categoryService.getCategory(categoryId); 35 | return ResponseEntity.ok(categoryDto); 36 | } 37 | 38 | // Build Get All Categories REST API 39 | @GetMapping 40 | public ResponseEntity> getCategories(){ 41 | return ResponseEntity.ok(categoryService.getAllCategories()); 42 | } 43 | 44 | // Build Update Category REST API 45 | @PreAuthorize("hasRole('ADMIN')") 46 | @PutMapping("{id}") 47 | public ResponseEntity updateCategory(@RequestBody CategoryDto categoryDto, 48 | @PathVariable("id") Long categoryId){ 49 | return ResponseEntity.ok(categoryService.updateCategory(categoryDto, categoryId)); 50 | } 51 | 52 | // Build Delete Category REST API 53 | @PreAuthorize("hasRole('ADMIN')") 54 | @DeleteMapping("{id}") 55 | public ResponseEntity deleteCategory(@PathVariable("id") Long categoryId){ 56 | categoryService.deleteCategory(categoryId); 57 | return ResponseEntity.ok("Category deleted successfully!."); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/controller/CommentController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.controller; 2 | 3 | import com.springboot.blog.payload.CommentDto; 4 | import com.springboot.blog.service.CommentService; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import jakarta.validation.Valid; 10 | import java.util.List; 11 | 12 | @RestController 13 | @RequestMapping("/api/v1") 14 | public class CommentController { 15 | 16 | private CommentService commentService; 17 | 18 | public CommentController(CommentService commentService) { 19 | this.commentService = commentService; 20 | } 21 | 22 | @PostMapping("/posts/{postId}/comments") 23 | public ResponseEntity createComment(@PathVariable(value = "postId") long postId, 24 | @Valid @RequestBody CommentDto commentDto){ 25 | return new ResponseEntity<>(commentService.createComment(postId, commentDto), HttpStatus.CREATED); 26 | } 27 | 28 | @GetMapping("/posts/{postId}/comments") 29 | public List getCommentsByPostId(@PathVariable(value = "postId") Long postId){ 30 | return commentService.getCommentsByPostId(postId); 31 | } 32 | 33 | @GetMapping("/posts/{postId}/comments/{id}") 34 | public ResponseEntity getCommentById(@PathVariable(value = "postId") Long postId, 35 | @PathVariable(value = "id") Long commentId){ 36 | CommentDto commentDto = commentService.getCommentById(postId, commentId); 37 | return new ResponseEntity<>(commentDto, HttpStatus.OK); 38 | } 39 | 40 | @PutMapping("/posts/{postId}/comments/{id}") 41 | public ResponseEntity updateComment(@PathVariable(value = "postId") Long postId, 42 | @PathVariable(value = "id") Long commentId, 43 | @Valid @RequestBody CommentDto commentDto){ 44 | CommentDto updatedComment = commentService.updateComment(postId, commentId, commentDto); 45 | return new ResponseEntity<>(updatedComment, HttpStatus.OK); 46 | } 47 | 48 | @DeleteMapping("/posts/{postId}/comments/{id}") 49 | public ResponseEntity deleteComment(@PathVariable(value = "postId") Long postId, 50 | @PathVariable(value = "id") Long commentId){ 51 | commentService.deleteComment(postId, commentId); 52 | return new ResponseEntity<>("Comment deleted successfully", HttpStatus.OK); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/controller/PostController.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.controller; 2 | 3 | import com.springboot.blog.payload.PostDto; 4 | import com.springboot.blog.payload.PostResponse; 5 | import com.springboot.blog.service.PostService; 6 | import com.springboot.blog.utils.AppConstants; 7 | import io.swagger.v3.oas.annotations.Operation; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 9 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 10 | import io.swagger.v3.oas.annotations.tags.Tag; 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 jakarta.validation.Valid; 17 | import java.util.List; 18 | 19 | @RestController 20 | @RequestMapping("/api/posts") 21 | @Tag( 22 | name = "CRUD REST APIs for Post Resource" 23 | ) 24 | public class PostController { 25 | 26 | private PostService postService; 27 | 28 | public PostController(PostService postService) { 29 | this.postService = postService; 30 | } 31 | 32 | @Operation( 33 | summary = "Create Post REST API", 34 | description = "Create Post REST API is used to save post into database" 35 | ) 36 | @ApiResponse( 37 | responseCode = "201", 38 | description = "Http Status 201 CREATED" 39 | ) 40 | @SecurityRequirement( 41 | name = "Bear Authentication" 42 | ) 43 | // create blog post rest api 44 | @PreAuthorize("hasRole('ADMIN')") 45 | @PostMapping 46 | public ResponseEntity createPost(@Valid @RequestBody PostDto postDto){ 47 | return new ResponseEntity<>(postService.createPost(postDto), HttpStatus.CREATED); 48 | } 49 | 50 | @Operation( 51 | summary = "Get All Posts REST API", 52 | description = "Get All Posts REST API is used to fetch all the posts from the database" 53 | ) 54 | @ApiResponse( 55 | responseCode = "200", 56 | description = "Http Status 200 SUCCESS" 57 | ) 58 | // get all posts rest api 59 | @GetMapping 60 | public PostResponse getAllPosts( 61 | @RequestParam(value = "pageNo", defaultValue = AppConstants.DEFAULT_PAGE_NUMBER, required = false) int pageNo, 62 | @RequestParam(value = "pageSize", defaultValue = AppConstants.DEFAULT_PAGE_SIZE, required = false) int pageSize, 63 | @RequestParam(value = "sortBy", defaultValue = AppConstants.DEFAULT_SORT_BY, required = false) String sortBy, 64 | @RequestParam(value = "sortDir", defaultValue = AppConstants.DEFAULT_SORT_DIRECTION, required = false) String sortDir 65 | ){ 66 | return postService.getAllPosts(pageNo, pageSize, sortBy, sortDir); 67 | } 68 | 69 | @Operation( 70 | summary = "Get Post By Id REST API", 71 | description = "Get Post By Id REST API is used to get single post from the database" 72 | ) 73 | @ApiResponse( 74 | responseCode = "200", 75 | description = "Http Status 200 SUCCESS" 76 | ) 77 | // get post by id 78 | @GetMapping("/{id}") 79 | public ResponseEntity getPostById(@PathVariable(name = "id") long id){ 80 | return ResponseEntity.ok(postService.getPostById(id)); 81 | } 82 | 83 | @Operation( 84 | summary = "update Post REST API", 85 | description = "Update Post REST API is used to update a particular post in the database" 86 | ) 87 | @ApiResponse( 88 | responseCode = "200", 89 | description = "Http Status 200 SUCCESS" 90 | ) 91 | @SecurityRequirement( 92 | name = "Bear Authentication" 93 | ) 94 | // update post by id rest api 95 | @PreAuthorize("hasRole('ADMIN')") 96 | @PutMapping("/{id}") 97 | public ResponseEntity updatePost(@Valid @RequestBody PostDto postDto, @PathVariable(name = "id") long id){ 98 | 99 | PostDto postResponse = postService.updatePost(postDto, id); 100 | 101 | return new ResponseEntity<>(postResponse, HttpStatus.OK); 102 | } 103 | 104 | @Operation( 105 | summary = "Delete Post REST API", 106 | description = "Delete Post REST API is used to delete a particular post from the database" 107 | ) 108 | @ApiResponse( 109 | responseCode = "200", 110 | description = "Http Status 200 SUCCESS" 111 | ) 112 | @SecurityRequirement( 113 | name = "Bear Authentication" 114 | ) 115 | // delete post rest api 116 | @PreAuthorize("hasRole('ADMIN')") 117 | @DeleteMapping("/{id}") 118 | public ResponseEntity deletePost(@PathVariable(name = "id") long id){ 119 | 120 | postService.deletePostById(id); 121 | 122 | return new ResponseEntity<>("Post entity deleted successfully.", HttpStatus.OK); 123 | } 124 | 125 | // Build Get Posts by Category REST API 126 | // http://localhost:8080/api/posts/category/3 127 | @GetMapping("/category/{id}") 128 | public ResponseEntity> getPostsByCategory(@PathVariable("id") Long categoryId){ 129 | List postDtos = postService.getPostsByCategory(categoryId); 130 | return ResponseEntity.ok(postDtos); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/entity/Category.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.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.util.List; 10 | 11 | @Getter 12 | @Setter 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Entity 16 | @Table(name = "categories") 17 | public class Category { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | private String name; 23 | private String description; 24 | 25 | @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true) 26 | private List posts; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/entity/Comment.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import jakarta.persistence.*; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | 13 | @Entity 14 | @Table(name = "comments") 15 | public class Comment { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private long id; 20 | 21 | private String name; 22 | private String email; 23 | private String body; 24 | 25 | @ManyToOne(fetch = FetchType.LAZY) 26 | @JoinColumn(name = "post_id", nullable = false) 27 | private Post post; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/entity/Post.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.entity; 2 | 3 | import lombok.*; 4 | 5 | import jakarta.persistence.*; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | 9 | @Getter 10 | @Setter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | 14 | @Entity 15 | @Table( 16 | name = "posts", uniqueConstraints = {@UniqueConstraint(columnNames = {"title"})} 17 | ) 18 | public class Post { 19 | 20 | @Id 21 | @GeneratedValue( 22 | strategy = GenerationType.IDENTITY 23 | ) 24 | private Long id; 25 | 26 | @Column(name = "title", nullable = false) 27 | private String title; 28 | 29 | @Column(name = "description", nullable = false) 30 | private String description; 31 | 32 | @Column(name = "content", nullable = false) 33 | private String content; 34 | 35 | @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) 36 | private Set comments = new HashSet<>(); 37 | 38 | @ManyToOne(fetch = FetchType.LAZY) 39 | @JoinColumn(name = "category_id") 40 | private Category category; 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/entity/Role.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Entity 14 | @Table(name = "roles") 15 | public class Role { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | private String name; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.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.util.Set; 10 | 11 | @Setter 12 | @Getter 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Entity 16 | @Table(name = "users") 17 | public class User { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | private String name; 23 | @Column(nullable = false, unique = true) 24 | private String username; 25 | @Column(nullable = false, unique = true) 26 | private String email; 27 | @Column(nullable = false) 28 | private String password; 29 | 30 | @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 31 | @JoinTable(name = "users_roles", 32 | joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), 33 | inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id") 34 | ) 35 | private Set roles; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/exception/BlogAPIException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class BlogAPIException extends RuntimeException { 6 | 7 | private HttpStatus status; 8 | private String message; 9 | 10 | public BlogAPIException(HttpStatus status, String message) { 11 | this.status = status; 12 | this.message = message; 13 | } 14 | 15 | public BlogAPIException(String message, HttpStatus status, String message1) { 16 | super(message); 17 | this.status = status; 18 | this.message = message1; 19 | } 20 | 21 | public HttpStatus getStatus() { 22 | return status; 23 | } 24 | 25 | @Override 26 | public String getMessage() { 27 | return message; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/exception/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.exception; 2 | 3 | import com.springboot.blog.payload.ErrorDetails; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.HttpStatusCode; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.access.AccessDeniedException; 9 | import org.springframework.validation.FieldError; 10 | import org.springframework.web.bind.MethodArgumentNotValidException; 11 | import org.springframework.web.bind.annotation.ControllerAdvice; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.context.request.WebRequest; 14 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 15 | 16 | import java.util.Date; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | @ControllerAdvice 21 | public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { 22 | 23 | // handle specific exceptions 24 | @ExceptionHandler(ResourceNotFoundException.class) 25 | public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException exception, 26 | WebRequest webRequest){ 27 | ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(), 28 | webRequest.getDescription(false)); 29 | return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND); 30 | } 31 | 32 | @ExceptionHandler(BlogAPIException.class) 33 | public ResponseEntity handleBlogAPIException(BlogAPIException exception, 34 | WebRequest webRequest){ 35 | ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(), 36 | webRequest.getDescription(false)); 37 | return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); 38 | } 39 | // global exceptions 40 | @ExceptionHandler(Exception.class) 41 | public ResponseEntity handleGlobalException(Exception exception, 42 | WebRequest webRequest){ 43 | ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(), 44 | webRequest.getDescription(false)); 45 | return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR); 46 | } 47 | 48 | @Override 49 | protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, 50 | HttpHeaders headers, 51 | HttpStatusCode status, 52 | WebRequest request) { 53 | Map errors = new HashMap<>(); 54 | ex.getBindingResult().getAllErrors().forEach((error) ->{ 55 | String fieldName = ((FieldError)error).getField(); 56 | String message = error.getDefaultMessage(); 57 | errors.put(fieldName, message); 58 | }); 59 | 60 | return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); 61 | } 62 | 63 | // @ExceptionHandler(MethodArgumentNotValidException.class) 64 | // public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException exception, 65 | // WebRequest webRequest){ 66 | // Map errors = new HashMap<>(); 67 | // exception.getBindingResult().getAllErrors().forEach((error) ->{ 68 | // String fieldName = ((FieldError)error).getField(); 69 | // String message = error.getDefaultMessage(); 70 | // errors.put(fieldName, message); 71 | // }); 72 | // return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); 73 | // } 74 | 75 | @ExceptionHandler(AccessDeniedException.class) 76 | public ResponseEntity handleAccessDeniedException(AccessDeniedException exception, 77 | WebRequest webRequest){ 78 | ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(), 79 | webRequest.getDescription(false)); 80 | return new ResponseEntity<>(errorDetails, HttpStatus.UNAUTHORIZED); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ResponseStatus; 5 | 6 | @ResponseStatus(value = HttpStatus.NOT_FOUND) 7 | public class ResourceNotFoundException extends RuntimeException{ 8 | private String resourceName; 9 | private String fieldName; 10 | private long fieldValue; 11 | 12 | public ResourceNotFoundException(String resourceName, String fieldName, long fieldValue) { 13 | super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); // Post not found with id : 1 14 | this.resourceName = resourceName; 15 | this.fieldName = fieldName; 16 | this.fieldValue = fieldValue; 17 | } 18 | 19 | public String getResourceName() { 20 | return resourceName; 21 | } 22 | 23 | public String getFieldName() { 24 | return fieldName; 25 | } 26 | 27 | public long getFieldValue() { 28 | return fieldValue; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/CategoryDto.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class CategoryDto { 13 | private Long id; 14 | private String name; 15 | private String description; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/CommentDto.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import lombok.Data; 4 | 5 | import jakarta.validation.constraints.Email; 6 | import jakarta.validation.constraints.NotEmpty; 7 | import jakarta.validation.constraints.Size; 8 | 9 | @Data 10 | public class CommentDto { 11 | private long id; 12 | // name should not be null or empty 13 | @NotEmpty(message = "Name should not be null or empty") 14 | private String name; 15 | 16 | // email should not be null or empty 17 | // email field validation 18 | @NotEmpty(message = "Email should not be null or empty") 19 | @Email 20 | private String email; 21 | 22 | // comment body should not be bull or empty 23 | // Comment body must be minimum 10 characters 24 | @NotEmpty 25 | @Size(min = 10, message = "Comment body must be minimum 10 characters") 26 | private String body; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/ErrorDetails.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import java.util.Date; 4 | 5 | public class ErrorDetails { 6 | private Date timestamp; 7 | private String message; 8 | private String details; 9 | 10 | public ErrorDetails(Date timestamp, String message, String details) { 11 | this.timestamp = timestamp; 12 | this.message = message; 13 | this.details = details; 14 | } 15 | 16 | public Date getTimestamp() { 17 | return timestamp; 18 | } 19 | 20 | public String getMessage() { 21 | return message; 22 | } 23 | 24 | public String getDetails() { 25 | return details; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/JWTAuthResponse.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Setter 9 | @Getter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class JWTAuthResponse { 13 | private String accessToken; 14 | private String tokenType = "Bearer"; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/LoginDto.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Setter 9 | @Getter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class LoginDto { 13 | private String usernameOrEmail; 14 | private String password; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/PostDto.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Data; 5 | 6 | import jakarta.validation.constraints.NotEmpty; 7 | import jakarta.validation.constraints.Size; 8 | import java.util.Set; 9 | 10 | @Data 11 | @Schema( 12 | description = "PostDto Model Information" 13 | ) 14 | public class PostDto { 15 | private long id; 16 | 17 | @Schema( 18 | description = "Blog Post Title" 19 | ) 20 | // title should not be null or empty 21 | // title should have at least 2 characters 22 | @NotEmpty 23 | @Size(min = 2, message = "Post title should have at least 2 characters") 24 | private String title; 25 | 26 | @Schema( 27 | description = "Blog Post Description" 28 | ) 29 | // post description should be not null or empty 30 | // post description should have at least 10 characters 31 | @NotEmpty 32 | @Size(min = 10, message = "Post description should have at least 10 characters") 33 | private String description; 34 | 35 | @Schema( 36 | description = "Blog Post Content" 37 | ) 38 | // post content should not be null or empty 39 | @NotEmpty 40 | private String content; 41 | private Set comments; 42 | 43 | @Schema( 44 | description = "Blog Post Category" 45 | ) 46 | private Long categoryId; 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/PostResponse.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class PostResponse { 13 | private List content; 14 | private int pageNo; 15 | private int pageSize; 16 | private long totalElements; 17 | private int totalPages; 18 | private boolean last; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/payload/RegisterDto.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.payload; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Setter 9 | @Getter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class RegisterDto { 13 | private String name; 14 | private String username; 15 | private String email; 16 | private String password; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/repository/CategoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.repository; 2 | 3 | import com.springboot.blog.entity.Category; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface CategoryRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/repository/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.repository; 2 | 3 | import com.springboot.blog.entity.Comment; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface CommentRepository extends JpaRepository { 9 | List findByPostId(long postId); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/repository/PostRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.repository; 2 | 3 | import com.springboot.blog.entity.Post; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface PostRepository extends JpaRepository { 9 | 10 | List findByCategoryId(Long categoryId); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.repository; 2 | 3 | import com.springboot.blog.entity.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface RoleRepository extends JpaRepository { 9 | Optional findByName(String name); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.repository; 2 | 3 | import com.springboot.blog.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface UserRepository extends JpaRepository { 9 | 10 | Optional findByEmail(String email); 11 | 12 | Optional findByUsernameOrEmail(String username, String email); 13 | 14 | Optional findByUsername(String username); 15 | 16 | Boolean existsByUsername(String username); 17 | 18 | Boolean existsByEmail(String email); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/security/CustomUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.security; 2 | 3 | import com.springboot.blog.entity.User; 4 | import com.springboot.blog.repository.UserRepository; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | public class CustomUserDetailsService implements UserDetailsService { 17 | 18 | private UserRepository userRepository; 19 | 20 | public CustomUserDetailsService(UserRepository userRepository) { 21 | this.userRepository = userRepository; 22 | } 23 | 24 | @Override 25 | public UserDetails loadUserByUsername(String usernameOrEmail) throws UsernameNotFoundException { 26 | User user = userRepository.findByUsernameOrEmail(usernameOrEmail, usernameOrEmail) 27 | .orElseThrow(() -> 28 | new UsernameNotFoundException("User not found with username or email: "+ usernameOrEmail)); 29 | 30 | Set authorities = user 31 | .getRoles() 32 | .stream() 33 | .map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toSet()); 34 | 35 | return new org.springframework.security.core.userdetails.User(user.getEmail(), 36 | user.getPassword(), 37 | authorities); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.security; 2 | 3 | import jakarta.servlet.ServletException; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.springframework.security.core.AuthenticationException; 7 | import org.springframework.security.web.AuthenticationEntryPoint; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.IOException; 11 | 12 | @Component 13 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 14 | 15 | @Override 16 | public void commence(HttpServletRequest request, 17 | HttpServletResponse response, 18 | AuthenticationException authException) throws IOException, ServletException { 19 | 20 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/security/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.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.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.StringUtils; 14 | import org.springframework.web.filter.OncePerRequestFilter; 15 | 16 | import java.io.IOException; 17 | 18 | @Component 19 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 20 | 21 | private JwtTokenProvider jwtTokenProvider; 22 | 23 | private UserDetailsService userDetailsService; 24 | 25 | public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) { 26 | this.jwtTokenProvider = jwtTokenProvider; 27 | this.userDetailsService = userDetailsService; 28 | } 29 | 30 | @Override 31 | protected void doFilterInternal(HttpServletRequest request, 32 | HttpServletResponse response, 33 | FilterChain filterChain) throws ServletException, IOException { 34 | 35 | // get JWT token from http request 36 | String token = getTokenFromRequest(request); 37 | 38 | // validate token 39 | if(StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)){ 40 | 41 | // get username from token 42 | String username = jwtTokenProvider.getUsername(token); 43 | 44 | // load the user associated with token 45 | UserDetails userDetails = userDetailsService.loadUserByUsername(username); 46 | 47 | UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( 48 | userDetails, 49 | null, 50 | userDetails.getAuthorities() 51 | ); 52 | 53 | authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 54 | 55 | SecurityContextHolder.getContext().setAuthentication(authenticationToken); 56 | 57 | } 58 | 59 | filterChain.doFilter(request, response); 60 | } 61 | 62 | private String getTokenFromRequest(HttpServletRequest request){ 63 | 64 | String bearerToken = request.getHeader("Authorization"); 65 | 66 | if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){ 67 | return bearerToken.substring(7, bearerToken.length()); 68 | } 69 | 70 | return null; 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/security/JwtTokenProvider.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.security; 2 | 3 | import com.springboot.blog.exception.BlogAPIException; 4 | import io.jsonwebtoken.ExpiredJwtException; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.MalformedJwtException; 7 | import io.jsonwebtoken.UnsupportedJwtException; 8 | import io.jsonwebtoken.io.Decoders; 9 | import io.jsonwebtoken.security.Keys; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.security.core.Authentication; 13 | import org.springframework.stereotype.Component; 14 | 15 | import javax.crypto.SecretKey; 16 | import java.security.Key; 17 | import java.util.Date; 18 | 19 | @Component 20 | public class JwtTokenProvider { 21 | 22 | @Value("${app.jwt-secret}") 23 | private String jwtSecret; 24 | 25 | @Value("${app-jwt-expiration-milliseconds}") 26 | private long jwtExpirationDate; 27 | 28 | // generate JWT token 29 | public String generateToken(Authentication authentication){ 30 | 31 | String username = authentication.getName(); 32 | 33 | Date currentDate = new Date(); 34 | 35 | Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate); 36 | 37 | String token = Jwts.builder() 38 | .subject(username) 39 | .issuedAt(new Date()) 40 | .expiration(expireDate) 41 | .signWith(key()) 42 | .compact(); 43 | 44 | return token; 45 | } 46 | 47 | private Key key(){ 48 | return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret)); 49 | } 50 | 51 | // get username from JWT token 52 | public String getUsername(String token){ 53 | 54 | return Jwts.parser() 55 | .verifyWith((SecretKey) key()) 56 | .build() 57 | .parseSignedClaims(token) 58 | .getPayload() 59 | .getSubject(); 60 | } 61 | 62 | // validate JWT token 63 | public boolean validateToken(String token){ 64 | try{ 65 | Jwts.parser() 66 | .verifyWith((SecretKey) key()) 67 | .build() 68 | .parse(token); 69 | return true; 70 | }catch (MalformedJwtException malformedJwtException){ 71 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Invalid JWT Token"); 72 | }catch (ExpiredJwtException expiredJwtException){ 73 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Expired JWT token"); 74 | }catch (UnsupportedJwtException unsupportedJwtException){ 75 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Unsupported JWT token"); 76 | }catch (IllegalArgumentException illegalArgumentException){ 77 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Jwt claims string is null or empty"); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/AuthService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service; 2 | 3 | import com.springboot.blog.payload.LoginDto; 4 | import com.springboot.blog.payload.RegisterDto; 5 | 6 | public interface AuthService { 7 | String login(LoginDto loginDto); 8 | 9 | String register(RegisterDto registerDto); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service; 2 | 3 | import com.springboot.blog.payload.CategoryDto; 4 | 5 | import java.util.List; 6 | 7 | public interface CategoryService { 8 | CategoryDto addCategory(CategoryDto categoryDto); 9 | 10 | CategoryDto getCategory(Long categoryId); 11 | 12 | List getAllCategories(); 13 | 14 | CategoryDto updateCategory(CategoryDto categoryDto, Long categoryId); 15 | 16 | void deleteCategory(Long categoryId); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service; 2 | 3 | import com.springboot.blog.payload.CommentDto; 4 | 5 | import java.util.List; 6 | 7 | public interface CommentService { 8 | CommentDto createComment(long postId, CommentDto commentDto); 9 | 10 | List getCommentsByPostId(long postId); 11 | 12 | CommentDto getCommentById(Long postId, Long commentId); 13 | 14 | CommentDto updateComment(Long postId, long commentId, CommentDto commentRequest); 15 | 16 | void deleteComment(Long postId, Long commentId); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/PostService.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service; 2 | 3 | import com.springboot.blog.payload.PostDto; 4 | import com.springboot.blog.payload.PostResponse; 5 | 6 | import java.util.List; 7 | 8 | public interface PostService { 9 | PostDto createPost(PostDto postDto); 10 | 11 | PostResponse getAllPosts(int pageNo, int pageSize, String sortBy, String sortDir); 12 | 13 | PostDto getPostById(long id); 14 | 15 | PostDto updatePost(PostDto postDto, long id); 16 | 17 | void deletePostById(long id); 18 | 19 | List getPostsByCategory(Long categoryId); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/impl/AuthServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service.impl; 2 | 3 | import com.springboot.blog.entity.Role; 4 | import com.springboot.blog.entity.User; 5 | import com.springboot.blog.exception.BlogAPIException; 6 | import com.springboot.blog.payload.LoginDto; 7 | import com.springboot.blog.payload.RegisterDto; 8 | import com.springboot.blog.repository.RoleRepository; 9 | import com.springboot.blog.repository.UserRepository; 10 | import com.springboot.blog.security.JwtTokenProvider; 11 | import com.springboot.blog.service.AuthService; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.security.authentication.AuthenticationManager; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 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.util.HashSet; 21 | import java.util.Set; 22 | 23 | @Service 24 | public class AuthServiceImpl implements AuthService { 25 | 26 | private AuthenticationManager authenticationManager; 27 | private UserRepository userRepository; 28 | private RoleRepository roleRepository; 29 | private PasswordEncoder passwordEncoder; 30 | private JwtTokenProvider jwtTokenProvider; 31 | 32 | 33 | public AuthServiceImpl(AuthenticationManager authenticationManager, 34 | UserRepository userRepository, 35 | RoleRepository roleRepository, 36 | PasswordEncoder passwordEncoder, 37 | JwtTokenProvider jwtTokenProvider) { 38 | this.authenticationManager = authenticationManager; 39 | this.userRepository = userRepository; 40 | this.roleRepository = roleRepository; 41 | this.passwordEncoder = passwordEncoder; 42 | this.jwtTokenProvider = jwtTokenProvider; 43 | } 44 | 45 | @Override 46 | public String login(LoginDto loginDto) { 47 | 48 | Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken( 49 | loginDto.getUsernameOrEmail(), loginDto.getPassword())); 50 | 51 | SecurityContextHolder.getContext().setAuthentication(authentication); 52 | 53 | String token = jwtTokenProvider.generateToken(authentication); 54 | 55 | return token; 56 | } 57 | 58 | @Override 59 | public String register(RegisterDto registerDto) { 60 | 61 | // add check for username exists in database 62 | if(userRepository.existsByUsername(registerDto.getUsername())){ 63 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Username is already exists!."); 64 | } 65 | 66 | // add check for email exists in database 67 | if(userRepository.existsByEmail(registerDto.getEmail())){ 68 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Email is already exists!."); 69 | } 70 | 71 | User user = new User(); 72 | user.setName(registerDto.getName()); 73 | user.setUsername(registerDto.getUsername()); 74 | user.setEmail(registerDto.getEmail()); 75 | user.setPassword(passwordEncoder.encode(registerDto.getPassword())); 76 | 77 | Set roles = new HashSet<>(); 78 | Role userRole = roleRepository.findByName("ROLE_USER").get(); 79 | roles.add(userRole); 80 | user.setRoles(roles); 81 | 82 | userRepository.save(user); 83 | 84 | return "User registered successfully!."; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/impl/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service.impl; 2 | 3 | import com.springboot.blog.entity.Category; 4 | import com.springboot.blog.exception.ResourceNotFoundException; 5 | import com.springboot.blog.payload.CategoryDto; 6 | import com.springboot.blog.repository.CategoryRepository; 7 | import com.springboot.blog.service.CategoryService; 8 | import org.modelmapper.ModelMapper; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @Service 15 | public class CategoryServiceImpl implements CategoryService { 16 | 17 | private CategoryRepository categoryRepository; 18 | private ModelMapper modelMapper; 19 | 20 | public CategoryServiceImpl(CategoryRepository categoryRepository, ModelMapper modelMapper) { 21 | this.categoryRepository = categoryRepository; 22 | this.modelMapper = modelMapper; 23 | } 24 | 25 | @Override 26 | public CategoryDto addCategory(CategoryDto categoryDto) { 27 | Category category = modelMapper.map(categoryDto, Category.class); 28 | Category savedCategory = categoryRepository.save(category); 29 | return modelMapper.map(savedCategory, CategoryDto.class); 30 | } 31 | 32 | @Override 33 | public CategoryDto getCategory(Long categoryId) { 34 | 35 | Category category = categoryRepository.findById(categoryId) 36 | .orElseThrow(() -> new ResourceNotFoundException("Category", "id", categoryId)); 37 | 38 | return modelMapper.map(category, CategoryDto.class); 39 | } 40 | 41 | @Override 42 | public List getAllCategories() { 43 | 44 | List categories = categoryRepository.findAll(); 45 | 46 | return categories.stream().map((category) -> modelMapper.map(category, CategoryDto.class)) 47 | .collect(Collectors.toList()); 48 | } 49 | 50 | @Override 51 | public CategoryDto updateCategory(CategoryDto categoryDto, Long categoryId) { 52 | 53 | Category category = categoryRepository.findById(categoryId) 54 | .orElseThrow(() -> new ResourceNotFoundException("Category", "id", categoryId)); 55 | 56 | category.setName(categoryDto.getName()); 57 | category.setDescription(categoryDto.getDescription()); 58 | category.setId(categoryId); 59 | 60 | Category updatedCategory = categoryRepository.save(category); 61 | 62 | return modelMapper.map(updatedCategory, CategoryDto.class); 63 | } 64 | 65 | @Override 66 | public void deleteCategory(Long categoryId) { 67 | 68 | Category category = categoryRepository.findById(categoryId) 69 | .orElseThrow(() -> new ResourceNotFoundException("Category", "id", categoryId)); 70 | 71 | categoryRepository.delete(category); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/impl/CommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service.impl; 2 | 3 | import com.springboot.blog.entity.Comment; 4 | import com.springboot.blog.entity.Post; 5 | import com.springboot.blog.exception.BlogAPIException; 6 | import com.springboot.blog.exception.ResourceNotFoundException; 7 | import com.springboot.blog.payload.CommentDto; 8 | import com.springboot.blog.repository.CommentRepository; 9 | import com.springboot.blog.repository.PostRepository; 10 | import com.springboot.blog.service.CommentService; 11 | import org.modelmapper.ModelMapper; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | @Service 19 | public class CommentServiceImpl implements CommentService { 20 | 21 | private CommentRepository commentRepository; 22 | private PostRepository postRepository; 23 | private ModelMapper mapper; 24 | public CommentServiceImpl(CommentRepository commentRepository, PostRepository postRepository, ModelMapper mapper) { 25 | this.commentRepository = commentRepository; 26 | this.postRepository = postRepository; 27 | this.mapper = mapper; 28 | } 29 | 30 | @Override 31 | public CommentDto createComment(long postId, CommentDto commentDto) { 32 | 33 | Comment comment = mapToEntity(commentDto); 34 | 35 | // retrieve post entity by id 36 | Post post = postRepository.findById(postId).orElseThrow( 37 | () -> new ResourceNotFoundException("Post", "id", postId)); 38 | 39 | // set post to comment entity 40 | comment.setPost(post); 41 | 42 | // comment entity to DB 43 | Comment newComment = commentRepository.save(comment); 44 | 45 | return mapToDTO(newComment); 46 | } 47 | 48 | @Override 49 | public List getCommentsByPostId(long postId) { 50 | // retrieve comments by postId 51 | List comments = commentRepository.findByPostId(postId); 52 | 53 | // convert list of comment entities to list of comment dto's 54 | return comments.stream().map(comment -> mapToDTO(comment)).collect(Collectors.toList()); 55 | } 56 | 57 | @Override 58 | public CommentDto getCommentById(Long postId, Long commentId) { 59 | // retrieve post entity by id 60 | Post post = postRepository.findById(postId).orElseThrow( 61 | () -> new ResourceNotFoundException("Post", "id", postId)); 62 | 63 | // retrieve comment by id 64 | Comment comment = commentRepository.findById(commentId).orElseThrow(() -> 65 | new ResourceNotFoundException("Comment", "id", commentId)); 66 | 67 | if(!comment.getPost().getId().equals(post.getId())){ 68 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Comment does not belong to post"); 69 | } 70 | 71 | return mapToDTO(comment); 72 | } 73 | 74 | @Override 75 | public CommentDto updateComment(Long postId, long commentId, CommentDto commentRequest) { 76 | // retrieve post entity by id 77 | Post post = postRepository.findById(postId).orElseThrow( 78 | () -> new ResourceNotFoundException("Post", "id", postId)); 79 | 80 | // retrieve comment by id 81 | Comment comment = commentRepository.findById(commentId).orElseThrow(() -> 82 | new ResourceNotFoundException("Comment", "id", commentId)); 83 | 84 | if(!comment.getPost().getId().equals(post.getId())){ 85 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Comment does not belongs to post"); 86 | } 87 | 88 | comment.setName(commentRequest.getName()); 89 | comment.setEmail(commentRequest.getEmail()); 90 | comment.setBody(commentRequest.getBody()); 91 | 92 | Comment updatedComment = commentRepository.save(comment); 93 | return mapToDTO(updatedComment); 94 | } 95 | 96 | @Override 97 | public void deleteComment(Long postId, Long commentId) { 98 | // retrieve post entity by id 99 | Post post = postRepository.findById(postId).orElseThrow( 100 | () -> new ResourceNotFoundException("Post", "id", postId)); 101 | 102 | // retrieve comment by id 103 | Comment comment = commentRepository.findById(commentId).orElseThrow(() -> 104 | new ResourceNotFoundException("Comment", "id", commentId)); 105 | 106 | if(!comment.getPost().getId().equals(post.getId())){ 107 | throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Comment does not belongs to post"); 108 | } 109 | 110 | commentRepository.delete(comment); 111 | } 112 | 113 | private CommentDto mapToDTO(Comment comment){ 114 | CommentDto commentDto = mapper.map(comment, CommentDto.class); 115 | 116 | // CommentDto commentDto = new CommentDto(); 117 | // commentDto.setId(comment.getId()); 118 | // commentDto.setName(comment.getName()); 119 | // commentDto.setEmail(comment.getEmail()); 120 | // commentDto.setBody(comment.getBody()); 121 | return commentDto; 122 | } 123 | 124 | private Comment mapToEntity(CommentDto commentDto){ 125 | Comment comment = mapper.map(commentDto, Comment.class); 126 | // Comment comment = new Comment(); 127 | // comment.setId(commentDto.getId()); 128 | // comment.setName(commentDto.getName()); 129 | // comment.setEmail(commentDto.getEmail()); 130 | // comment.setBody(commentDto.getBody()); 131 | return comment; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/service/impl/PostServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.service.impl; 2 | 3 | import com.springboot.blog.entity.Category; 4 | import com.springboot.blog.entity.Post; 5 | import com.springboot.blog.exception.ResourceNotFoundException; 6 | import com.springboot.blog.payload.PostDto; 7 | import com.springboot.blog.payload.PostResponse; 8 | import com.springboot.blog.repository.CategoryRepository; 9 | import com.springboot.blog.repository.PostRepository; 10 | import com.springboot.blog.service.PostService; 11 | import org.modelmapper.ModelMapper; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.PageRequest; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.data.domain.Sort; 16 | import org.springframework.stereotype.Service; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | @Service 22 | public class PostServiceImpl implements PostService { 23 | 24 | private PostRepository postRepository; 25 | 26 | private ModelMapper mapper; 27 | 28 | private CategoryRepository categoryRepository; 29 | 30 | public PostServiceImpl(PostRepository postRepository, ModelMapper mapper, 31 | CategoryRepository categoryRepository) { 32 | this.postRepository = postRepository; 33 | this.mapper = mapper; 34 | this.categoryRepository = categoryRepository; 35 | } 36 | 37 | @Override 38 | public PostDto createPost(PostDto postDto) { 39 | 40 | Category category = categoryRepository.findById(postDto.getCategoryId()) 41 | .orElseThrow(() -> new ResourceNotFoundException("Category", "id", postDto.getCategoryId())); 42 | 43 | // convert DTO to entity 44 | Post post = mapToEntity(postDto); 45 | post.setCategory(category); 46 | Post newPost = postRepository.save(post); 47 | 48 | // convert entity to DTO 49 | PostDto postResponse = mapToDTO(newPost); 50 | return postResponse; 51 | } 52 | 53 | @Override 54 | public PostResponse getAllPosts(int pageNo, int pageSize, String sortBy, String sortDir) { 55 | 56 | Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending() 57 | : Sort.by(sortBy).descending(); 58 | 59 | // create Pageable instance 60 | Pageable pageable = PageRequest.of(pageNo, pageSize, sort); 61 | 62 | Page posts = postRepository.findAll(pageable); 63 | 64 | // get content for page object 65 | List listOfPosts = posts.getContent(); 66 | 67 | List content= listOfPosts.stream().map(post -> mapToDTO(post)).collect(Collectors.toList()); 68 | 69 | PostResponse postResponse = new PostResponse(); 70 | postResponse.setContent(content); 71 | postResponse.setPageNo(posts.getNumber()); 72 | postResponse.setPageSize(posts.getSize()); 73 | postResponse.setTotalElements(posts.getTotalElements()); 74 | postResponse.setTotalPages(posts.getTotalPages()); 75 | postResponse.setLast(posts.isLast()); 76 | 77 | return postResponse; 78 | } 79 | 80 | @Override 81 | public PostDto getPostById(long id) { 82 | Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); 83 | return mapToDTO(post); 84 | } 85 | 86 | @Override 87 | public PostDto updatePost(PostDto postDto, long id) { 88 | // get post by id from the database 89 | Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); 90 | 91 | Category category = categoryRepository.findById(postDto.getCategoryId()) 92 | .orElseThrow(() -> new ResourceNotFoundException("Category", "id", postDto.getCategoryId())); 93 | 94 | post.setTitle(postDto.getTitle()); 95 | post.setDescription(postDto.getDescription()); 96 | post.setContent(postDto.getContent()); 97 | post.setCategory(category); 98 | Post updatedPost = postRepository.save(post); 99 | return mapToDTO(updatedPost); 100 | } 101 | 102 | @Override 103 | public void deletePostById(long id) { 104 | // get post by id from the database 105 | Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id)); 106 | postRepository.delete(post); 107 | } 108 | 109 | @Override 110 | public List getPostsByCategory(Long categoryId) { 111 | 112 | Category category = categoryRepository.findById(categoryId) 113 | .orElseThrow(() -> new ResourceNotFoundException("Category", "id", categoryId)); 114 | 115 | List posts = postRepository.findByCategoryId(categoryId); 116 | 117 | return posts.stream().map((post) -> mapToDTO(post)) 118 | .collect(Collectors.toList()); 119 | } 120 | 121 | // convert Entity into DTO 122 | private PostDto mapToDTO(Post post){ 123 | PostDto postDto = mapper.map(post, PostDto.class); 124 | // PostDto postDto = new PostDto(); 125 | // postDto.setId(post.getId()); 126 | // postDto.setTitle(post.getTitle()); 127 | // postDto.setDescription(post.getDescription()); 128 | // postDto.setContent(post.getContent()); 129 | return postDto; 130 | } 131 | 132 | // convert DTO to entity 133 | private Post mapToEntity(PostDto postDto){ 134 | Post post = mapper.map(postDto, Post.class); 135 | // Post post = new Post(); 136 | // post.setTitle(postDto.getTitle()); 137 | // post.setDescription(postDto.getDescription()); 138 | // post.setContent(postDto.getContent()); 139 | return post; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.utils; 2 | 3 | public class AppConstants { 4 | 5 | public static final String DEFAULT_PAGE_NUMBER = "0"; 6 | public static final String DEFAULT_PAGE_SIZE = "10"; 7 | public static final String DEFAULT_SORT_BY = "id"; 8 | public static final String DEFAULT_SORT_DIRECTION = "asc"; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/springboot/blog/utils/PasswordGeneratorEncoder.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog.utils; 2 | 3 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 4 | import org.springframework.security.crypto.password.PasswordEncoder; 5 | 6 | public class PasswordGeneratorEncoder { 7 | public static void main(String[] args) { 8 | PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); 9 | System.out.println(passwordEncoder.encode("admin")); 10 | System.out.println(passwordEncoder.encode("ramesh")); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url = jdbc:mysql://localhost:3306/myblog?useSSL=false&serverTimezone=UTC 2 | spring.datasource.username = root 3 | spring.datasource.password = root 4 | 5 | # hibernate properties 6 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect 7 | 8 | # Hibernate ddl auto (create, create-drop, validate, update) 9 | spring.jpa.hibernate.ddl-auto = update 10 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url = jdbc:mysql://javaguides.ccbtqawd66uq.us-east-2.rds.amazonaws.com:3306/myblog?useSSL=false&serverTimezone=UTC 2 | spring.datasource.username = root 3 | spring.datasource.password = Mysql123$ 4 | 5 | # hibernate properties 6 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect 7 | 8 | # Hibernate ddl auto (create, create-drop, validate, update) 9 | spring.jpa.hibernate.ddl-auto = update 10 | 11 | server.port=5000 12 | -------------------------------------------------------------------------------- /src/main/resources/application-qa.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url = jdbc:mysql://localhost:3306/myblog?useSSL=false&serverTimezone=UTC 2 | spring.datasource.username = root 3 | spring.datasource.password = root 4 | 5 | # hibernate properties 6 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect 7 | 8 | # Hibernate ddl auto (create, create-drop, validate, update) 9 | spring.jpa.hibernate.ddl-auto = update 10 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url = jdbc:mysql://localhost:3306/myblog?useSSL=false&serverTimezone=UTC 2 | spring.datasource.username = root 3 | spring.datasource.password = Mysql@123 4 | 5 | # hibernate properties 6 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect 7 | 8 | # Hibernate ddl auto (create, create-drop, validate, update) 9 | spring.jpa.hibernate.ddl-auto = update 10 | 11 | app.jwt-secret=daf66e01593f61a15b857cf433aae03a005812b31234e149036bcc8dee755dbb 12 | app-jwt-expiration-milliseconds=604800000 -------------------------------------------------------------------------------- /src/test/java/com/springboot/blog/SpringbootBlogRestApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.springboot.blog; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringbootBlogRestApiApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------