├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── images ├── 493fa437-762b-4c29-bb9d-d70134b18269.jpg └── 8adbcee3-6e67-4c43-8f1b-861ecddce27f.jpg ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── lakshy │ │ └── blog │ │ ├── BloggingAppApisApplication.java │ │ ├── config │ │ ├── AppConstants.java │ │ ├── SecurityConfig.java │ │ └── SwaggerConfig.java │ │ ├── controllers │ │ ├── AuthController.java │ │ ├── CategoryController.java │ │ ├── CommentController.java │ │ ├── PostController.java │ │ └── UserController.java │ │ ├── entities │ │ ├── Category.java │ │ ├── Comment.java │ │ ├── Post.java │ │ ├── Role.java │ │ └── User.java │ │ ├── exceptions │ │ ├── ApiException.java │ │ ├── GlobalExceptionHandler.java │ │ ├── IncorrectFileFormatException.java │ │ └── ResourceNotFoundException.java │ │ ├── payloads │ │ ├── ApiResponse.java │ │ ├── CategoryDto.java │ │ ├── CommentDto.java │ │ ├── JwtAuthRequest.java │ │ ├── JwtAuthResponse.java │ │ ├── PostDto.java │ │ ├── PostResponse.java │ │ ├── RoleDto.java │ │ └── UserDto.java │ │ ├── repositories │ │ ├── CategoryRepo.java │ │ ├── CommentRepo.java │ │ ├── PostRepo.java │ │ ├── RoleRepo.java │ │ └── UserRepo.java │ │ ├── security │ │ ├── CustomUserDetailService.java │ │ ├── JwtAuthenticationEntryPoint.java │ │ ├── JwtAuthenticationFilter.java │ │ └── JwtTokenHelper.java │ │ └── services │ │ ├── CategoryService.java │ │ ├── CommentService.java │ │ ├── FileService.java │ │ ├── PostService.java │ │ ├── UserService.java │ │ └── impl │ │ ├── CategoryServiceImpl.java │ │ ├── CommentServiceImpl.java │ │ ├── FileServiceImpl.java │ │ ├── PostServiceImpl.java │ │ └── UserServiceImpl.java └── resources │ └── application.properties └── test └── java └── com └── lakshy └── blog └── BloggingAppApisApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lakshygupta/Blog-App-APIs/58f1ab9309b45d63d2d7484d4657c5461b820097/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blog Application Backend 2 | A RESTful API for creating, reading and managing the posts/blogs. Developed using SpringBoot this REST api provides fast and secure access to anything you need. Documentation provided below. 3 | 4 | ## Built with 5 | - [Spring Boot](https://spring.io/) 6 | - [Maven](https://maven.apache.org/) 7 | - [JPA](https://spring.io/projects/spring-data-jpa) 8 | - [Hibernate](https://hibernate.org/) 9 | - [MySQL](https://www.mysql.com/) 10 | 11 | ## Overview 12 | Users should be able to create posts, Each post should have a category/topic tagged to it. Signup & login functionality of users along with password hashing. Pagination and sorting on posts. Users should be able to fetch the posts for other users or can view all the post of a particular topic. Data validation on create/update endpoints. 13 | 14 | ## Features Include 15 | - Posts CRUD 16 | - Users CRUD 17 | - Category CRUD 18 | - Comments on posts CRUD 19 | - Post limiting for pagination 20 | - Post sorting 21 | - Role based authentication 22 | - Custom Exception handling 23 | - JWT authentication 24 | - DTO pattern 25 | - Image upload 26 | - Post searching by keyword 27 | - Role specific API access 28 | - Data Validation using Hibernate validator 29 | - Documentation using Swagger 30 | 31 | ## ER Diagram 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /images/493fa437-762b-4c29-bb9d-d70134b18269.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lakshygupta/Blog-App-APIs/58f1ab9309b45d63d2d7484d4657c5461b820097/images/493fa437-762b-4c29-bb9d-d70134b18269.jpg -------------------------------------------------------------------------------- /images/8adbcee3-6e67-4c43-8f1b-861ecddce27f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lakshygupta/Blog-App-APIs/58f1ab9309b45d63d2d7484d4657c5461b820097/images/8adbcee3-6e67-4c43-8f1b-861ecddce27f.jpg -------------------------------------------------------------------------------- /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 /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /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 "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\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/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq 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%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.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% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.2 9 | 10 | 11 | com.lakshy.blog 12 | blogging-app-apis 13 | 0.0.1-SNAPSHOT 14 | blogging-app-apis 15 | Backend for Blog app using Spring Boot 16 | 17 | 11 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 | org.springframework.boot 30 | spring-boot-starter-security 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-validation 35 | 36 | 37 | io.springfox 38 | springfox-boot-starter 39 | 3.0.0 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-devtools 44 | runtime 45 | true 46 | 47 | 48 | mysql 49 | mysql-connector-java 50 | runtime 51 | 52 | 53 | io.jsonwebtoken 54 | jjwt 55 | 0.9.1 56 | 57 | 58 | org.projectlombok 59 | lombok 60 | true 61 | 62 | 63 | org.modelmapper 64 | modelmapper 65 | 3.1.0 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-test 70 | test 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-maven-plugin 79 | 80 | 81 | 82 | org.projectlombok 83 | lombok 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/BloggingAppApisApplication.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog; 2 | 3 | import java.util.List; 4 | 5 | import org.modelmapper.ModelMapper; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | 13 | import com.lakshy.blog.config.AppConstants; 14 | import com.lakshy.blog.entities.Role; 15 | import com.lakshy.blog.repositories.RoleRepo; 16 | 17 | @SpringBootApplication 18 | public class BloggingAppApisApplication implements CommandLineRunner { 19 | 20 | @Autowired 21 | private PasswordEncoder passwordEncoder; 22 | 23 | @Autowired 24 | private RoleRepo roleRepo; 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(BloggingAppApisApplication.class, args); 28 | } 29 | 30 | @Bean 31 | public ModelMapper modelMapper() 32 | { 33 | return new ModelMapper(); 34 | } 35 | 36 | // will run automatically when main runs and passing arguments via terminal 37 | @Override 38 | public void run(String... args) throws Exception { 39 | 40 | // System.out.println(this.passwordEncoder.encode("12345")); 41 | 42 | // Creating 2 roles at the start of application 43 | try { 44 | 45 | Role role = new Role(); 46 | role.setId(AppConstants.ADMIN_USER); 47 | role.setName("ROLE_ADMIN"); 48 | 49 | Role role1 = new Role(); 50 | role1.setId(AppConstants.NORMAL_USER); 51 | role1.setName("ROLE_NORMAL"); 52 | 53 | List roles = List.of(role,role1); 54 | 55 | List result = this.roleRepo.saveAll(roles); 56 | 57 | result.forEach(r -> { 58 | System.out.println(r.getName()); 59 | }); 60 | 61 | } catch (Exception e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/config/AppConstants.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.config; 2 | 3 | // All constants and hard coded values 4 | public class AppConstants { 5 | public static final String PAGE_NUMBER = "0"; 6 | public static final String PAGE_SIZE = "10"; 7 | public static final String SOTRT_BY = "postId"; 8 | public static final String SORT_DIR = "asc"; 9 | public static final Integer NORMAL_USER = 502; 10 | public static final Integer ADMIN_USER = 501; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.HttpMethod; 7 | import org.springframework.security.authentication.AuthenticationManager; 8 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 9 | import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; 10 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 11 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 12 | import org.springframework.security.web.SecurityFilterChain; 13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 15 | 16 | import com.lakshy.blog.security.CustomUserDetailService; 17 | import com.lakshy.blog.security.JwtAuthenticationEntryPoint; 18 | import com.lakshy.blog.security.JwtAuthenticationFilter; 19 | 20 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 21 | import org.springframework.security.config.http.SessionCreationPolicy; 22 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 23 | import org.springframework.security.crypto.password.PasswordEncoder; 24 | 25 | @Configuration 26 | @EnableWebSecurity 27 | @EnableWebMvc 28 | @EnableGlobalMethodSecurity(prePostEnabled = true) 29 | public class SecurityConfig { 30 | 31 | public static final String[] PUBLIC_URLS = { 32 | "/api/v1/auth/**", 33 | "/v3/api-docs", 34 | "/v3/api-docs/**", 35 | "/v2/api-docs", 36 | "/swagger-resources/**", 37 | "/swagger-ui/**", 38 | "/webjars/**" 39 | }; 40 | 41 | @Autowired 42 | private CustomUserDetailService customUserDetailService; 43 | 44 | @Autowired 45 | private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; 46 | 47 | @Autowired 48 | private JwtAuthenticationFilter jwtAuthenticationFilter; 49 | 50 | 51 | @Bean 52 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 53 | http 54 | .csrf() 55 | .disable() 56 | .authorizeHttpRequests() 57 | .antMatchers(PUBLIC_URLS).permitAll() 58 | .antMatchers(HttpMethod.GET).permitAll() 59 | .anyRequest() 60 | .authenticated() 61 | .and() 62 | .exceptionHandling() 63 | .authenticationEntryPoint(this.jwtAuthenticationEntryPoint) 64 | .and() 65 | .sessionManagement() 66 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 67 | 68 | http 69 | .addFilterBefore(this.jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); 70 | return http.build(); 71 | } 72 | 73 | 74 | /* 75 | * === No Need for authenticationManager in Spring Security >= 5.7.2 === 76 | * https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter 77 | * It is also no longer necessary to manually set UserDetailsService implementation in AuthenticationManager instance, 78 | * it only needs to exist in the spring context (example is done with the help of annotations to create beans). 79 | */ 80 | // @Bean 81 | // AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) throws Exception { 82 | // return builder.userDetailsService(this.customUserDetailService).passwordEncoder(passwordEncoder()).and().build(); 83 | // } 84 | 85 | @Bean 86 | public PasswordEncoder passwordEncoder() { 87 | return new BCryptPasswordEncoder(); 88 | } 89 | 90 | @Bean 91 | public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { 92 | return authenticationConfiguration.getAuthenticationManager(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.config; 2 | 3 | import java.util.Collection; 4 | import java.util.Collections; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 9 | 10 | import springfox.documentation.builders.PathSelectors; 11 | import springfox.documentation.builders.RequestHandlerSelectors; 12 | import springfox.documentation.service.ApiInfo; 13 | import springfox.documentation.service.Contact; 14 | import springfox.documentation.spi.DocumentationType; 15 | import springfox.documentation.spring.web.plugins.Docket; 16 | 17 | @Configuration 18 | @EnableWebMvc 19 | public class SwaggerConfig { 20 | @Bean 21 | public Docket api() { 22 | return new Docket(DocumentationType.SWAGGER_2).apiInfo(getInfo()) 23 | .select().apis(RequestHandlerSelectors.any()).paths(PathSelectors.any()).build(); 24 | } 25 | 26 | private ApiInfo getInfo() { 27 | 28 | return new ApiInfo("Blog App APIs", "Backend API for Blogging App using Java SpringBoot", "1.0", "Terms of Service", new Contact("Lakshy Gupta", "https://github.com/lakshygupta", "lakshygupta99@gmail.com"), "License of APIs", "API License URL", Collections.emptyList()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/controllers/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.authentication.AuthenticationManager; 7 | import org.springframework.security.authentication.BadCredentialsException; 8 | import org.springframework.security.authentication.DisabledException; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.userdetails.UserDetails; 11 | import org.springframework.security.core.userdetails.UserDetailsService; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import com.lakshy.blog.exceptions.ApiException; 18 | import com.lakshy.blog.payloads.JwtAuthRequest; 19 | import com.lakshy.blog.payloads.JwtAuthResponse; 20 | import com.lakshy.blog.payloads.UserDto; 21 | import com.lakshy.blog.security.JwtTokenHelper; 22 | import com.lakshy.blog.services.UserService; 23 | 24 | @RestController 25 | @RequestMapping("/api/v1/auth") 26 | public class AuthController { 27 | 28 | @Autowired 29 | private JwtTokenHelper jwtTokenHelper; 30 | 31 | @Autowired 32 | private UserDetailsService userDetailService; 33 | 34 | @Autowired 35 | private AuthenticationManager authenticationManager; 36 | 37 | @Autowired 38 | private UserService userService; 39 | 40 | @PostMapping("/login") 41 | public ResponseEntity createToken( 42 | @RequestBody JwtAuthRequest request 43 | ) throws Exception{ 44 | 45 | this.authenticate(request.getUsername(),request.getPassword()); 46 | UserDetails userDetails = this.userDetailService.loadUserByUsername(request.getUsername()); 47 | 48 | String token = this.jwtTokenHelper.generateToken(userDetails); 49 | 50 | JwtAuthResponse response = new JwtAuthResponse(); 51 | response.setToken(token); 52 | 53 | return new ResponseEntity(response,HttpStatus.OK); 54 | } 55 | 56 | private void authenticate(String username, String password) throws Exception { 57 | UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password); 58 | // we might get exceptions here i.e. user disabled handling it in global exception 59 | try { 60 | this.authenticationManager.authenticate(usernamePasswordAuthenticationToken); 61 | } 62 | catch(BadCredentialsException ex) { 63 | System.out.println("invalid details of user in request"); 64 | throw new ApiException("Invalid Username or password"); 65 | } 66 | } 67 | 68 | // register new user 69 | @PostMapping("/register") 70 | public ResponseEntity registerNewUser(@RequestBody UserDto userDto){ 71 | UserDto newUser = this.userService.registerNewUser(userDto); 72 | 73 | return new ResponseEntity(newUser,HttpStatus.OK); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/controllers/CategoryController.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.controllers; 2 | 3 | import java.util.List; 4 | 5 | import javax.validation.Valid; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.web.bind.annotation.DeleteMapping; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.PutMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import com.lakshy.blog.payloads.ApiResponse; 21 | import com.lakshy.blog.payloads.CategoryDto; 22 | import com.lakshy.blog.services.CategoryService; 23 | 24 | @RestController 25 | @RequestMapping("/api/categories") 26 | public class CategoryController { 27 | 28 | @Autowired 29 | private CategoryService categoryService; 30 | 31 | @PostMapping("/") 32 | public ResponseEntity createCategory(@Valid @RequestBody CategoryDto categoryDto) 33 | { 34 | CategoryDto savedCategory = categoryService.createCategory(categoryDto); 35 | return new ResponseEntity(savedCategory, HttpStatus.CREATED); 36 | } 37 | 38 | @PutMapping("/{categoryId}") 39 | public ResponseEntity updateCategory(@Valid @RequestBody CategoryDto categoryDto, @PathVariable Integer categoryId) 40 | { 41 | CategoryDto updatedCategory = categoryService.updateCategory(categoryDto, categoryId); 42 | return new ResponseEntity(updatedCategory, HttpStatus.OK); 43 | } 44 | 45 | // Admin only 46 | @PreAuthorize("hasRole('ADMIN')") 47 | @DeleteMapping("/{categoryId}") 48 | public ResponseEntity deleteCategory(@PathVariable Integer categoryId) 49 | { 50 | categoryService.deleteCategory(categoryId); 51 | return new ResponseEntity(new ApiResponse("Category deleted Successfully",true),HttpStatus.OK); 52 | } 53 | 54 | @GetMapping("/{categoryId}") 55 | public ResponseEntity getCategoryById(@PathVariable Integer categoryId) 56 | { 57 | CategoryDto categoryDto = categoryService.getCategoryById(categoryId); 58 | return new ResponseEntity(categoryDto,HttpStatus.OK); 59 | } 60 | 61 | @GetMapping("/") 62 | public ResponseEntity> getAllCategories() 63 | { 64 | List categories = categoryService.getAllCategories(); 65 | return new ResponseEntity>(categories,HttpStatus.OK); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/controllers/CommentController.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.controllers; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.DeleteMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 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 | import com.lakshy.blog.payloads.ApiResponse; 15 | import com.lakshy.blog.payloads.CommentDto; 16 | import com.lakshy.blog.services.CommentService; 17 | 18 | @RestController 19 | @RequestMapping("/api") 20 | public class CommentController { 21 | 22 | @Autowired 23 | private CommentService commentService; 24 | 25 | @PostMapping("/post/{postId}/user/{userId}/comments") 26 | public ResponseEntity createComment(@RequestBody CommentDto comment,@PathVariable Integer postId,@PathVariable Integer userId) 27 | { 28 | CommentDto createComment = this.commentService.createComment(comment, postId, userId); 29 | return new ResponseEntity(createComment,HttpStatus.CREATED); 30 | } 31 | 32 | // Admin only 33 | @PreAuthorize("hasRole('ADMIN')") 34 | @DeleteMapping("/comments/{commentId}") 35 | public ResponseEntity deleteComment(@PathVariable Integer commentId) 36 | { 37 | this.commentService.deleteComment(commentId); 38 | return new ResponseEntity(new ApiResponse("Comment deleted successfully",true),HttpStatus.OK); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/controllers/PostController.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.controllers; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.List; 7 | 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.hibernate.engine.jdbc.StreamUtils; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.security.access.prepost.PreAuthorize; 17 | import org.springframework.web.bind.annotation.DeleteMapping; 18 | import org.springframework.web.bind.annotation.GetMapping; 19 | import org.springframework.web.bind.annotation.PathVariable; 20 | import org.springframework.web.bind.annotation.PostMapping; 21 | import org.springframework.web.bind.annotation.PutMapping; 22 | import org.springframework.web.bind.annotation.RequestBody; 23 | import org.springframework.web.bind.annotation.RequestMapping; 24 | import org.springframework.web.bind.annotation.RequestParam; 25 | import org.springframework.web.bind.annotation.RestController; 26 | import org.springframework.web.multipart.MultipartFile; 27 | 28 | import com.lakshy.blog.config.AppConstants; 29 | import com.lakshy.blog.payloads.ApiResponse; 30 | import com.lakshy.blog.payloads.PostDto; 31 | import com.lakshy.blog.payloads.PostResponse; 32 | import com.lakshy.blog.services.FileService; 33 | import com.lakshy.blog.services.PostService; 34 | 35 | @RestController 36 | @RequestMapping("/api") 37 | public class PostController { 38 | 39 | @Autowired 40 | private PostService postService; 41 | 42 | @Autowired 43 | private FileService fileService; 44 | 45 | //path from application.properties to upload images 46 | @Value("${project.image}") 47 | private String PATH; 48 | 49 | @PostMapping("/user/{userId}/category/{categoryId}/posts") 50 | public ResponseEntity createPost(@RequestBody PostDto postDto, @PathVariable Integer userId, @PathVariable Integer categoryId) 51 | { 52 | PostDto savedPost = this.postService.createPost(postDto, userId, categoryId); 53 | return new ResponseEntity(savedPost,HttpStatus.CREATED); 54 | } 55 | 56 | // get posts by category 57 | @GetMapping("/category/{categoryId}/posts") 58 | public ResponseEntity getPostsByCategory( 59 | @PathVariable Integer categoryId, 60 | @RequestParam(value="pageNumber",defaultValue = AppConstants.PAGE_NUMBER,required = false) Integer pageNumber, 61 | @RequestParam(value="pageSize",defaultValue = AppConstants.PAGE_SIZE,required = false) Integer pageSize 62 | ) 63 | { 64 | PostResponse postResponse = this.postService.getPostsByCategory(categoryId,pageNumber,pageSize); 65 | return new ResponseEntity(postResponse,HttpStatus.OK); 66 | } 67 | 68 | // get posts by user 69 | @GetMapping("/user/{userId}/posts") 70 | public ResponseEntity> getPostsByUser(@PathVariable Integer userId) 71 | { 72 | List posts = this.postService.getPostsByUsers(userId); 73 | return new ResponseEntity>(posts,HttpStatus.OK); 74 | } 75 | 76 | @GetMapping("/posts/{postId}") 77 | public ResponseEntity getPostById(@PathVariable Integer postId) 78 | { 79 | PostDto post = this.postService.getPostById(postId); 80 | return new ResponseEntity(post,HttpStatus.OK); 81 | } 82 | 83 | @GetMapping("/posts") 84 | public ResponseEntity> getAllPosts(){ 85 | List posts = this.postService.getAllPosts(); 86 | return new ResponseEntity>(posts,HttpStatus.OK); 87 | } 88 | 89 | // Admin only 90 | @PreAuthorize("hasRole('ADMIN')") 91 | @DeleteMapping("/posts/{postId}") 92 | public ResponseEntity deletePost(@PathVariable Integer postId) 93 | { 94 | postService.deletePost(postId); 95 | return new ResponseEntity(new ApiResponse("Post Deleted successfully",true),HttpStatus.OK); 96 | } 97 | 98 | @PutMapping("/posts/{postId}") 99 | public ResponseEntity updatePost(@RequestBody PostDto postDto,@PathVariable Integer postId) 100 | { 101 | System.out.println("here"); 102 | PostDto post = postService.updatePost(postDto, postId); 103 | return new ResponseEntity(post,HttpStatus.OK); 104 | } 105 | 106 | // pagination and sorting 107 | @GetMapping("/postsPage") 108 | public ResponseEntity getAllPostsByPage( 109 | @RequestParam(value="pageNumber",defaultValue = AppConstants.PAGE_NUMBER,required = false) Integer pageNumber, 110 | @RequestParam(value="pageSize",defaultValue = AppConstants.PAGE_SIZE,required = false) Integer pageSize, 111 | @RequestParam(value="sortBy", defaultValue = AppConstants.SOTRT_BY, required = false) String sortBy, 112 | @RequestParam(value="sortDir", defaultValue = AppConstants.SORT_DIR, required = false) String sortDir 113 | ){ 114 | PostResponse postResponse = this.postService.getAllPostsByPage(pageNumber,pageSize,sortBy,sortDir); 115 | return new ResponseEntity(postResponse,HttpStatus.OK); 116 | } 117 | 118 | // searching 119 | @GetMapping("/posts/search/{keywords}") 120 | public ResponseEntity> searchPostByTitle(@PathVariable("keywords") String keywords) 121 | { 122 | List result = this.postService.searchPost(keywords); 123 | return new ResponseEntity>(result,HttpStatus.OK); 124 | } 125 | 126 | //post image upload 127 | @PostMapping("/post/image/upload/{postId}") 128 | public ResponseEntity uploadPostImage( 129 | @RequestParam("image") MultipartFile image, 130 | @PathVariable Integer postId 131 | ) throws IOException 132 | { 133 | PostDto postDto = this.postService.getPostById(postId); 134 | String fileName = this.fileService.uploadImage(PATH, image); 135 | postDto.setImageName(fileName); 136 | PostDto updatePost = this.postService.updatePost(postDto, postId); 137 | return new ResponseEntity(updatePost,HttpStatus.OK); 138 | } 139 | 140 | // serve image using restapi 141 | @GetMapping(value ="post/image/{imageName}",produces = MediaType.IMAGE_JPEG_VALUE) 142 | public void serveImage( 143 | @PathVariable String imageName, 144 | HttpServletResponse response 145 | ) throws IOException 146 | { 147 | InputStream resource = this.fileService.getResources(PATH, imageName); 148 | response.setContentType(MediaType.IMAGE_JPEG_VALUE); 149 | StreamUtils.copy(resource, response.getOutputStream()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/controllers/UserController.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.controllers; 2 | 3 | import java.util.List; 4 | 5 | import javax.validation.Valid; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.web.bind.annotation.DeleteMapping; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.PostMapping; 15 | import org.springframework.web.bind.annotation.PutMapping; 16 | import org.springframework.web.bind.annotation.RequestBody; 17 | import org.springframework.web.bind.annotation.RequestMapping; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | import com.lakshy.blog.payloads.ApiResponse; 21 | import com.lakshy.blog.payloads.UserDto; 22 | import com.lakshy.blog.services.UserService; 23 | 24 | @RestController 25 | @RequestMapping("/api/users") 26 | public class UserController 27 | { 28 | @Autowired 29 | private UserService userService; 30 | 31 | @PostMapping("/") 32 | public ResponseEntity createUser(@Valid @RequestBody UserDto userDto) 33 | { 34 | UserDto createdUserDto = userService.createUser(userDto); 35 | return new ResponseEntity<>(createdUserDto,HttpStatus.CREATED); 36 | } 37 | 38 | @PutMapping("/{userId}") 39 | public ResponseEntity updateUser(@Valid @RequestBody UserDto userDto, @PathVariable("userId") Integer userId) 40 | { 41 | UserDto updatedUserDto = userService.updateUser(userDto, userId); 42 | return ResponseEntity.ok(updatedUserDto); 43 | } 44 | 45 | // Admin only 46 | @PreAuthorize("hasRole('ADMIN')") 47 | @DeleteMapping("/{userId}") 48 | public ResponseEntity deleteUser(@PathVariable Integer userId) // instead of ApiResponse we can also use ? as sometime we do not know the type 49 | { 50 | userService.deleteUser(userId); 51 | // return new ResponseEntity(Map.of("message","user Deleted successfully"),HttpStatus.OK); 52 | return new ResponseEntity(new ApiResponse("User Deleted Successfully",true),HttpStatus.OK); 53 | } 54 | 55 | @GetMapping("/{userId}") 56 | public ResponseEntity getUserById(@PathVariable Integer userId) 57 | { 58 | UserDto fetchedUser = userService.getUserById(userId); 59 | return new ResponseEntity(fetchedUser, HttpStatus.OK); 60 | } 61 | 62 | @GetMapping("/") 63 | public ResponseEntity> getALLUsers() 64 | { 65 | List users = userService.getAllUsers(); 66 | return new ResponseEntity>(users, HttpStatus.OK); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/entities/Category.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.entities; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Column; 8 | import javax.persistence.Entity; 9 | import javax.persistence.FetchType; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.OneToMany; 14 | import javax.persistence.Table; 15 | 16 | import lombok.Getter; 17 | import lombok.NoArgsConstructor; 18 | import lombok.Setter; 19 | 20 | 21 | @Entity 22 | @Table(name = "categories") 23 | @Getter 24 | @Setter 25 | @NoArgsConstructor 26 | public class Category { 27 | @Id 28 | @GeneratedValue(strategy = GenerationType.IDENTITY) 29 | private Integer categoryId; 30 | 31 | @Column(name="title",length=100,nullable=false) 32 | private String categoryTitle; 33 | 34 | @Column(name="description") 35 | private String categoryDescription; 36 | 37 | @OneToMany(mappedBy = "category", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 38 | private List posts = new ArrayList<>(); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/entities/Comment.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.entities; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.ManyToOne; 8 | import javax.persistence.Table; 9 | 10 | import lombok.Getter; 11 | import lombok.Setter; 12 | 13 | @Entity 14 | @Table(name="comments") 15 | @Getter 16 | @Setter 17 | public class Comment { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Integer id; 22 | 23 | private String content; 24 | 25 | @ManyToOne 26 | private Post post; 27 | 28 | @ManyToOne 29 | private User user; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/entities/Post.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.entities; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Date; 5 | import java.util.List; 6 | 7 | import javax.persistence.CascadeType; 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.FetchType; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.JoinColumn; 15 | import javax.persistence.ManyToOne; 16 | import javax.persistence.OneToMany; 17 | import javax.persistence.Table; 18 | 19 | import lombok.Getter; 20 | import lombok.NoArgsConstructor; 21 | import lombok.Setter; 22 | 23 | @Entity 24 | @Table(name = "posts") 25 | @Getter 26 | @Setter 27 | @NoArgsConstructor 28 | public class Post 29 | { 30 | @Id 31 | @GeneratedValue(strategy = GenerationType.IDENTITY) 32 | private Integer postId; 33 | 34 | @Column(nullable=false) 35 | private String title; 36 | 37 | @Column(length = 10000, nullable=false) 38 | private String content; 39 | 40 | private String imageName; 41 | 42 | private Date addedDate; 43 | 44 | @ManyToOne 45 | @JoinColumn(name = "category_id") 46 | private Category category; 47 | 48 | @ManyToOne 49 | private User user; 50 | 51 | @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 52 | private List comments = new ArrayList<>(); 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/entities/Role.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.entities; 2 | 3 | import javax.persistence.Entity; 4 | import javax.persistence.GeneratedValue; 5 | import javax.persistence.GenerationType; 6 | import javax.persistence.Id; 7 | import javax.persistence.Table; 8 | 9 | import lombok.Getter; 10 | import lombok.Setter; 11 | 12 | @Entity 13 | @Table(name = "Roles") 14 | @Getter 15 | @Setter 16 | public class Role { 17 | @Id 18 | private int id; 19 | 20 | private String name; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/entities/User.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.entities; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.HashSet; 6 | import java.util.List; 7 | import java.util.Set; 8 | import java.util.stream.Collectors; 9 | 10 | import javax.persistence.CascadeType; 11 | import javax.persistence.Column; 12 | import javax.persistence.Entity; 13 | import javax.persistence.FetchType; 14 | import javax.persistence.GeneratedValue; 15 | import javax.persistence.GenerationType; 16 | import javax.persistence.Id; 17 | import javax.persistence.JoinColumn; 18 | import javax.persistence.JoinTable; 19 | import javax.persistence.ManyToMany; 20 | import javax.persistence.ManyToOne; 21 | import javax.persistence.OneToMany; 22 | import javax.persistence.Table; 23 | 24 | import org.springframework.security.core.GrantedAuthority; 25 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 26 | import org.springframework.security.core.userdetails.UserDetails; 27 | 28 | import lombok.Getter; 29 | import lombok.NoArgsConstructor; 30 | import lombok.Setter; 31 | 32 | @Entity 33 | @Table(name="users") 34 | @NoArgsConstructor 35 | @Getter 36 | @Setter 37 | public class User implements UserDetails 38 | { 39 | @Id 40 | @GeneratedValue(strategy = GenerationType.IDENTITY) 41 | private Integer id; 42 | 43 | @Column(name="user_name", nullable = false, length = 100) 44 | private String name; 45 | 46 | private String email; 47 | 48 | private String password; 49 | 50 | private String about; 51 | 52 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 53 | private List posts = new ArrayList<>(); 54 | 55 | @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY) 56 | private List comments = new ArrayList<>(); 57 | 58 | @ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER) 59 | @JoinTable(name = "user_role", 60 | joinColumns=@JoinColumn(name="user",referencedColumnName = "id"), 61 | inverseJoinColumns = @JoinColumn(name="role", referencedColumnName = "id") 62 | ) 63 | private Set roles= new HashSet<>(); 64 | 65 | // methods from UserDetails as required by Spring Security 66 | @Override 67 | public Collection getAuthorities() { 68 | List authorities = this.roles.stream().map((role) -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()); 69 | return authorities; 70 | } 71 | 72 | @Override 73 | public String getUsername() { 74 | return this.email; 75 | } 76 | 77 | @Override 78 | public boolean isAccountNonExpired() { 79 | return true; 80 | } 81 | 82 | @Override 83 | public boolean isAccountNonLocked() { 84 | return true; 85 | } 86 | 87 | @Override 88 | public boolean isCredentialsNonExpired() { 89 | return true; 90 | } 91 | 92 | @Override 93 | public boolean isEnabled() { 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/exceptions/ApiException.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.exceptions; 2 | 3 | public class ApiException extends RuntimeException { 4 | 5 | public ApiException(String message) { 6 | super(message); 7 | 8 | } 9 | 10 | public ApiException() { 11 | super(); 12 | } 13 | 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/exceptions/GlobalExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.exceptions; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.validation.FieldError; 10 | import org.springframework.web.HttpRequestMethodNotSupportedException; 11 | import org.springframework.web.bind.MethodArgumentNotValidException; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.RestControllerAdvice; 14 | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; 15 | 16 | import com.lakshy.blog.payloads.ApiResponse; 17 | 18 | // will run for all exceptions which may come for all controller classes 19 | @RestControllerAdvice 20 | public class GlobalExceptionHandler { 21 | 22 | // tell which class we want to handle response 23 | @ExceptionHandler(ResourceNotFoundException.class) 24 | public ResponseEntity resourceNotFoundExceptionHandler(ResourceNotFoundException ex) 25 | { 26 | String message = ex.getMessage(); 27 | ApiResponse apiResponse = new ApiResponse(message, false); 28 | 29 | return new ResponseEntity(apiResponse,HttpStatus.NOT_FOUND); 30 | } 31 | 32 | // invalid password in login api 33 | @ExceptionHandler(ApiException.class) 34 | public ResponseEntity apiExceptionHandler(ApiException ex) 35 | { 36 | String message = ex.getMessage(); 37 | ApiResponse apiResponse = new ApiResponse(message, true); 38 | 39 | return new ResponseEntity(apiResponse,HttpStatus.BAD_REQUEST); 40 | } 41 | 42 | // not a custom created exception but raised by Validator on violation 43 | @ExceptionHandler(MethodArgumentNotValidException.class) 44 | public ResponseEntity> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex){ 45 | Map resp = new HashMap<>(); 46 | ex.getBindingResult().getAllErrors().forEach((error) -> { 47 | String fieldName = ((FieldError)error).getField(); 48 | String message = error.getDefaultMessage(); 49 | resp.put(fieldName, message); 50 | }); 51 | 52 | return new ResponseEntity>(resp,HttpStatus.BAD_REQUEST); 53 | } 54 | 55 | @ExceptionHandler(HttpRequestMethodNotSupportedException.class) 56 | public ResponseEntity> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) 57 | { 58 | Map resp = new HashMap<>(); 59 | resp.put("message", ex.getMessage()); 60 | resp.put("http request", ex.getMethod()); 61 | 62 | return new ResponseEntity>(resp,HttpStatus.BAD_REQUEST); 63 | } 64 | 65 | @ExceptionHandler(MethodArgumentTypeMismatchException.class) 66 | public ResponseEntity> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) 67 | { 68 | Map resp = new HashMap<>(); 69 | resp.put("message", ex.getMessage()); 70 | resp.put("name", ex.getName()); 71 | resp.put("value", " request parameter " + (String)ex.getValue()); 72 | return new ResponseEntity>(resp,HttpStatus.BAD_REQUEST); 73 | } 74 | 75 | // file is not an image handler 76 | @ExceptionHandler(IncorrectFileFormatException.class) 77 | public ResponseEntity IncorrectFileFormatExceptionHandler(IncorrectFileFormatException ex) 78 | { 79 | String message = ex.getMessage(); 80 | ApiResponse apiResponse = new ApiResponse(message, false); 81 | 82 | return new ResponseEntity(apiResponse,HttpStatus.NOT_FOUND); 83 | } 84 | 85 | // file not selected exception 86 | @ExceptionHandler(StringIndexOutOfBoundsException.class) 87 | public ResponseEntity> handleStringIndexOutOfBoundsExceptionException(StringIndexOutOfBoundsException ex) 88 | { 89 | Map resp = new HashMap<>(); 90 | resp.put("message", ex.getMessage()); 91 | resp.put("name", "Image File Not Found in Request"); 92 | return new ResponseEntity>(resp,HttpStatus.BAD_REQUEST); 93 | } 94 | 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/exceptions/IncorrectFileFormatException.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.exceptions; 2 | 3 | public class IncorrectFileFormatException extends RuntimeException { 4 | String fileType; 5 | 6 | public IncorrectFileFormatException(String fileType) { 7 | super(String.format("File Uploaded is not an Image (png/jpg/jpeg), Uploaded file is of type : %s",fileType)); 8 | this.fileType = fileType; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/exceptions/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.exceptions; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class ResourceNotFoundException extends RuntimeException { 9 | String resourceName; 10 | String FieldName; 11 | long FieldValue; 12 | String Value; 13 | 14 | public ResourceNotFoundException(String resourceName, String fieldName, long fieldValue) { 15 | super(String.format("%s not found with this %s : %s ", resourceName,fieldName, fieldValue)); 16 | this.resourceName = resourceName; 17 | FieldName = fieldName; 18 | FieldValue = fieldValue; 19 | } 20 | 21 | public ResourceNotFoundException(String resourceName, String fieldName, String value) { 22 | super(String.format("%s not found with this %s : %s ", resourceName,fieldName, value)); 23 | this.resourceName = resourceName; 24 | FieldName = fieldName; 25 | Value = value; 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 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 ApiResponse { 13 | private String message; 14 | private boolean success; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/CategoryDto.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import javax.validation.constraints.NotBlank; 4 | import javax.validation.constraints.Size; 5 | 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | @NoArgsConstructor 11 | @Getter 12 | @Setter 13 | public class CategoryDto { 14 | 15 | private Integer categoryId; 16 | @NotBlank 17 | @Size(min = 3,message = "length of title should be minimum 3") 18 | private String categoryTitle; 19 | 20 | @NotBlank 21 | @Size(min = 10, message = "length should be minimum 10") 22 | private String categoryDescription; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/CommentDto.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class CommentDto { 9 | 10 | private Integer id; 11 | 12 | private String content; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/JwtAuthRequest.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class JwtAuthRequest { 7 | 8 | private String username; 9 | 10 | private String password; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/JwtAuthResponse.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class JwtAuthResponse { 7 | private String token; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/PostDto.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import java.util.Date; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import com.lakshy.blog.entities.Comment; 8 | 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.Setter; 12 | 13 | 14 | @Getter 15 | @Setter 16 | @NoArgsConstructor 17 | public class PostDto { 18 | 19 | private Integer postId; 20 | 21 | private String title; 22 | 23 | private String content; 24 | 25 | private String imageName; 26 | 27 | private Date addedDate; 28 | 29 | private CategoryDto category; 30 | 31 | private UserDto user; 32 | 33 | private Set comments = new HashSet<>(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/PostResponse.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import java.util.List; 4 | 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | @Setter 12 | public class PostResponse { 13 | 14 | 15 | private List content; 16 | private int pageNumber; 17 | private int pageSize; 18 | private long totalElements; // total records 19 | private int totalPages; // total pages as per calculation of records 20 | private boolean lastPage; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/RoleDto.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class RoleDto { 7 | 8 | private int id; 9 | 10 | private String name; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/payloads/UserDto.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.payloads; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotEmpty; 8 | import javax.validation.constraints.Size; 9 | 10 | import com.fasterxml.jackson.annotation.JsonIgnore; 11 | import com.fasterxml.jackson.annotation.JsonProperty; 12 | import com.fasterxml.jackson.annotation.JsonProperty.Access; 13 | import com.lakshy.blog.entities.Role; 14 | 15 | import lombok.Getter; 16 | import lombok.NoArgsConstructor; 17 | import lombok.Setter; 18 | 19 | @NoArgsConstructor 20 | @Getter 21 | @Setter 22 | // use to transfer data from entities to services 23 | // entities classes are now used to store the data only 24 | // we can expose this DTO classes to apis and can use to get data from user 25 | 26 | public class UserDto 27 | { 28 | private Integer id; 29 | 30 | @NotEmpty 31 | @Size(min=4,message = "Username must be minimum of 4 characters") 32 | private String name; 33 | 34 | @Email(message = "Email address is not valid") 35 | 36 | private String email; 37 | 38 | @NotEmpty 39 | @Size(min=3,max=15, message = "Password must be 3 - 15 characters long") 40 | @JsonProperty(access = Access.WRITE_ONLY) 41 | // @Pattern(regexp = ) // add regular expression 42 | private String password; 43 | 44 | @NotEmpty 45 | private String about; 46 | 47 | private Set comments = new HashSet<>(); 48 | 49 | private Set roles= new HashSet<>(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/repositories/CategoryRepo.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.lakshy.blog.entities.Category; 6 | 7 | public interface CategoryRepo extends JpaRepository { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/repositories/CommentRepo.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.lakshy.blog.entities.Comment; 6 | 7 | public interface CommentRepo extends JpaRepository { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/repositories/PostRepo.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.repositories; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.data.domain.Pageable; 7 | import org.springframework.data.jpa.repository.JpaRepository; 8 | 9 | import com.lakshy.blog.entities.Category; 10 | import com.lakshy.blog.entities.Post; 11 | import com.lakshy.blog.entities.User; 12 | 13 | public interface PostRepo extends JpaRepository { 14 | 15 | // custom finder methods 16 | List findByUser(User user); 17 | Page findByCategory(Category category, Pageable pageable); 18 | 19 | // title wise search 20 | List findByTitleContaining(String title); 21 | 22 | /* Finding by param value using query 23 | * @Query("select p from Post p where p.title like :key") 24 | * List searchByTitle(@Param("key") String title); 25 | * 26 | * now to use this in Impl class before sending keyword to this add %Keyword_value_anything% 27 | */ 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/repositories/RoleRepo.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.lakshy.blog.entities.Role; 6 | 7 | public interface RoleRepo extends JpaRepository { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/repositories/UserRepo.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.repositories; 2 | 3 | import java.util.Optional; 4 | 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import com.lakshy.blog.entities.User; 8 | 9 | public interface UserRepo extends JpaRepository 10 | { 11 | Optional findByEmail(String email); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/security/CustomUserDetailService.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.security; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.security.core.userdetails.UserDetails; 5 | import org.springframework.security.core.userdetails.UserDetailsService; 6 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 7 | import org.springframework.stereotype.Service; 8 | 9 | import com.lakshy.blog.entities.User; 10 | import com.lakshy.blog.exceptions.ResourceNotFoundException; 11 | import com.lakshy.blog.repositories.UserRepo; 12 | 13 | @Service 14 | public class CustomUserDetailService implements UserDetailsService { 15 | // whenever spring security needs to know the user details,It will call this method 16 | 17 | @Autowired 18 | private UserRepo userRepo; 19 | 20 | @Override 21 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 22 | // loading user from database by username 23 | User user = this.userRepo.findByEmail(username).orElseThrow(() -> new ResourceNotFoundException("User", "Email", username)); 24 | return user; 25 | // since user has implemented UserDetails so returning User only 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.security; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.web.AuthenticationEntryPoint; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 15 | 16 | // it will execute when an unautherized user try to access autherized api 17 | @Override 18 | public void commence(HttpServletRequest request, HttpServletResponse response, 19 | AuthenticationException authException) throws IOException, ServletException { 20 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Access Denied"); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/security/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.security; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.FilterChain; 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.security.core.userdetails.UserDetails; 14 | import org.springframework.security.core.userdetails.UserDetailsService; 15 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.web.filter.OncePerRequestFilter; 18 | 19 | import io.jsonwebtoken.ExpiredJwtException; 20 | import io.jsonwebtoken.MalformedJwtException; 21 | 22 | @Component 23 | public class JwtAuthenticationFilter extends OncePerRequestFilter { 24 | 25 | @Autowired 26 | private UserDetailsService userDetailsService; 27 | 28 | @Autowired 29 | private JwtTokenHelper jwtTokenHelper; 30 | 31 | // this is called when we hit any api request 32 | @Override 33 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 34 | throws ServletException, IOException { 35 | 36 | // 1. get token from request from Authorization key in header 37 | String requestToken = request.getHeader("Authorization"); 38 | 39 | System.out.println(requestToken); 40 | 41 | String username = null; 42 | String token = null; 43 | 44 | if(requestToken != null && requestToken.startsWith("Bearer")) 45 | { 46 | token = requestToken.substring(7); // after bearer fetch original token 47 | try { 48 | username = this.jwtTokenHelper.getUsernameFromToken(token); 49 | 50 | } 51 | catch(IllegalArgumentException ex){ 52 | System.out.println("Unable to get JWT Token"); 53 | } 54 | catch(ExpiredJwtException ex) { 55 | System.out.println("JWT Token has expired"); 56 | } 57 | catch(MalformedJwtException ex) { 58 | System.out.println("Invalid JWT"); 59 | } 60 | 61 | } 62 | else 63 | { 64 | System.out.println("JWT Token is null or doesnot begin with bearer"); 65 | } 66 | 67 | // 2. we got the JWT token, Now validate the JWT Token 68 | if(username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 69 | // we have username and there is no security Authentication being applied over the apis 70 | // then only we need to validate the JWT Token 71 | 72 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 73 | 74 | if(this.jwtTokenHelper.validateToken(token, userDetails)) { 75 | // all fine - can authenticate here 76 | // we know spring security needs a username/password to get api authorized so creating this using details we have for user 77 | UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 78 | usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 79 | 80 | //set spring security 81 | SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); 82 | 83 | } else { 84 | // validation of token failed 85 | System.out.println("Invalid JWT Token"); 86 | } 87 | 88 | }else { 89 | // a security is already being defined in current application (basic db security or some other) 90 | // or the username is null for this JWT token 91 | System.out.println("username is null or context is not null"); 92 | } 93 | 94 | // if jwt token is valid then above conditions might have set the authentication context to authorize the api 95 | // otherwise we won't be able to access the API -> will hit the JwtAuthenticationEntryPoint > commence method 96 | // request will be forwarded in both the cases using below function 97 | filterChain.doFilter(request, response); 98 | 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/security/JwtTokenHelper.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.security; 2 | 3 | import java.util.Date; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.function.Function; 7 | 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.stereotype.Component; 10 | 11 | import io.jsonwebtoken.Claims; 12 | import io.jsonwebtoken.Jwts; 13 | import io.jsonwebtoken.SignatureAlgorithm; 14 | 15 | @Component 16 | public class JwtTokenHelper { 17 | public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60; 18 | 19 | 20 | private String secret = "jwtTokenKey"; 21 | 22 | //retrieve username from jwt token 23 | public String getUsernameFromToken(String token) { 24 | return getClaimFromToken(token, Claims::getSubject); 25 | } 26 | 27 | //retrieve expiration date from jwt token 28 | public Date getExpirationDateFromToken(String token) { 29 | return getClaimFromToken(token, Claims::getExpiration); 30 | } 31 | 32 | public T getClaimFromToken(String token, Function claimsResolver) { 33 | final Claims claims = getAllClaimsFromToken(token); 34 | return claimsResolver.apply(claims); 35 | } 36 | 37 | //for retrieveing any information from token we will need the secret key 38 | private Claims getAllClaimsFromToken(String token) { 39 | return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); 40 | } 41 | 42 | //check if the token has expired 43 | private Boolean isTokenExpired(String token) { 44 | final Date expiration = getExpirationDateFromToken(token); 45 | return expiration.before(new Date()); 46 | } 47 | 48 | //generate token for user 49 | public String generateToken(UserDetails userDetails) { 50 | Map claims = new HashMap<>(); 51 | return doGenerateToken(claims, userDetails.getUsername()); 52 | } 53 | 54 | //while creating the token - 55 | //1. Define claims of the token, like Issuer, Expiration, Subject, and the ID 56 | //2. Sign the JWT using the HS512 algorithm and secret key. 57 | //3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) 58 | // compaction of the JWT to a URL-safe string 59 | private String doGenerateToken(Map claims, String subject) { 60 | 61 | return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) 62 | .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) 63 | .signWith(SignatureAlgorithm.HS512, secret).compact(); 64 | } 65 | 66 | //validate token 67 | public Boolean validateToken(String token, UserDetails userDetails) { 68 | final String username = getUsernameFromToken(token); 69 | return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/CategoryService.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services; 2 | 3 | import java.util.List; 4 | 5 | import com.lakshy.blog.payloads.CategoryDto; 6 | 7 | public interface CategoryService { 8 | 9 | CategoryDto createCategory(CategoryDto categoryDto); 10 | 11 | CategoryDto updateCategory(CategoryDto categoryDto, Integer categoryId); 12 | 13 | void deleteCategory(Integer categoryId); 14 | 15 | List getAllCategories(); 16 | 17 | CategoryDto getCategoryById(Integer categoryId); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services; 2 | 3 | import com.lakshy.blog.payloads.CommentDto; 4 | 5 | public interface CommentService { 6 | 7 | CommentDto createComment(CommentDto commentDto, Integer postId, Integer userId); 8 | 9 | void deleteComment(Integer commentId); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/FileService.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | public interface FileService { 10 | 11 | String uploadImage(String path, MultipartFile file) throws IOException; 12 | 13 | InputStream getResources(String path, String fileName) throws FileNotFoundException; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/PostService.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services; 2 | 3 | import java.util.List; 4 | 5 | import com.lakshy.blog.payloads.PostDto; 6 | import com.lakshy.blog.payloads.PostResponse; 7 | 8 | public interface PostService { 9 | 10 | PostDto createPost(PostDto postDto, Integer userId, Integer categoryId); 11 | 12 | PostDto updatePost(PostDto postDto, Integer postId); 13 | 14 | void deletePost(Integer postId); 15 | 16 | List getAllPosts(); 17 | 18 | PostDto getPostById(Integer postId); 19 | 20 | // implementing pagination on getPostsByCategory, [view getPostsByUsers implementation for naive approach] 21 | //get all posts by category 22 | PostResponse getPostsByCategory(Integer categoryId, Integer pageNumber, Integer pageSize); 23 | 24 | // get all posts by User 25 | List getPostsByUsers(Integer userId); 26 | 27 | // search title posts by keyword 28 | List searchPost(String keyword); 29 | 30 | // pagination using JpaRepository -> to send its response we have a seperate PostResponse class in PayLoad 31 | PostResponse getAllPostsByPage(Integer pageNumber, Integer pageSize, String sortBy, String sortDir); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/UserService.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services; 2 | 3 | import java.util.List; 4 | 5 | import com.lakshy.blog.payloads.UserDto; 6 | 7 | public interface UserService 8 | { 9 | 10 | UserDto registerNewUser(UserDto user); 11 | 12 | UserDto createUser(UserDto user); 13 | 14 | UserDto updateUser(UserDto user, Integer userId); 15 | 16 | UserDto getUserById(Integer userId); 17 | 18 | List getAllUsers(); 19 | 20 | void deleteUser(Integer userId); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/impl/CategoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services.impl; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import org.modelmapper.ModelMapper; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import com.lakshy.blog.entities.Category; 11 | import com.lakshy.blog.exceptions.ResourceNotFoundException; 12 | import com.lakshy.blog.payloads.CategoryDto; 13 | import com.lakshy.blog.repositories.CategoryRepo; 14 | import com.lakshy.blog.services.CategoryService; 15 | 16 | @Service 17 | public class CategoryServiceImpl implements CategoryService { 18 | 19 | @Autowired 20 | private CategoryRepo categoryRepo; 21 | 22 | // model mappers 23 | @Autowired 24 | private ModelMapper modelMapper; 25 | 26 | private Category DtoToCategory(CategoryDto categoryDto) 27 | { 28 | Category category = modelMapper.map(categoryDto, Category.class); 29 | return category; 30 | } 31 | 32 | private CategoryDto CategoryToDto(Category category) 33 | { 34 | CategoryDto categoryDto = modelMapper.map(category, CategoryDto.class); 35 | return categoryDto; 36 | } 37 | 38 | // methods 39 | @Override 40 | public CategoryDto createCategory(CategoryDto categoryDto) { 41 | Category category = this.DtoToCategory(categoryDto); 42 | Category savedCategory = categoryRepo.save(category); 43 | return this.CategoryToDto(savedCategory); 44 | } 45 | 46 | @Override 47 | public CategoryDto updateCategory(CategoryDto categoryDto, Integer categoryId) { 48 | Category category = categoryRepo.findById(categoryId).orElseThrow(() -> new ResourceNotFoundException("Category", "Id", categoryId)); 49 | 50 | category.setCategoryDescription(categoryDto.getCategoryDescription()); 51 | category.setCategoryTitle(categoryDto.getCategoryTitle()); 52 | 53 | Category updatedCategory= categoryRepo.save(category); 54 | return this.CategoryToDto(updatedCategory); 55 | } 56 | 57 | @Override 58 | public void deleteCategory(Integer categoryId) { 59 | Category category = categoryRepo.findById(categoryId).orElseThrow(() -> new ResourceNotFoundException("Category","Id",categoryId)); 60 | 61 | categoryRepo.delete(category); 62 | } 63 | 64 | @Override 65 | public List getAllCategories() { 66 | List categories = categoryRepo.findAll(); 67 | 68 | List categoriesDto = categories.stream().map(cate -> this.CategoryToDto(cate)).collect(Collectors.toList()); 69 | return categoriesDto; 70 | } 71 | 72 | @Override 73 | public CategoryDto getCategoryById(Integer categoryId) { 74 | Category category = categoryRepo.findById(categoryId).orElseThrow(() -> new ResourceNotFoundException("Category","Id",categoryId)); 75 | return this.CategoryToDto(category); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/impl/CommentServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services.impl; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | 7 | import com.lakshy.blog.entities.Comment; 8 | import com.lakshy.blog.entities.Post; 9 | import com.lakshy.blog.entities.User; 10 | import com.lakshy.blog.exceptions.ResourceNotFoundException; 11 | import com.lakshy.blog.payloads.CommentDto; 12 | import com.lakshy.blog.repositories.CommentRepo; 13 | import com.lakshy.blog.repositories.PostRepo; 14 | import com.lakshy.blog.repositories.UserRepo; 15 | import com.lakshy.blog.services.CommentService; 16 | 17 | @Service 18 | public class CommentServiceImpl implements CommentService { 19 | 20 | @Autowired 21 | private PostRepo postRepo; 22 | 23 | @Autowired 24 | private CommentRepo commentRepo; 25 | 26 | @Autowired 27 | private UserRepo userRepo; 28 | 29 | @Autowired 30 | private ModelMapper modelMapper; 31 | 32 | @Override 33 | public CommentDto createComment(CommentDto commentDto, Integer postId, Integer userId) { 34 | Post post = this.postRepo.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "Id", postId)); 35 | User user = this.userRepo.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User", "Id", userId)); 36 | Comment comment = this.modelMapper.map(commentDto, Comment.class); 37 | comment.setPost(post); 38 | comment.setUser(user); 39 | Comment savedComment = commentRepo.save(comment); 40 | return this.modelMapper.map(savedComment, CommentDto.class); 41 | } 42 | 43 | @Override 44 | public void deleteComment(Integer commentId) { 45 | Comment comment = this.commentRepo.findById(commentId).orElseThrow(() -> new ResourceNotFoundException("Comment", "Id", commentId)); 46 | this.commentRepo.delete(comment); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/impl/FileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services.impl; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.nio.file.Files; 9 | import java.nio.file.Paths; 10 | import java.util.UUID; 11 | 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import com.lakshy.blog.exceptions.IncorrectFileFormatException; 16 | import com.lakshy.blog.exceptions.ResourceNotFoundException; 17 | import com.lakshy.blog.services.FileService; 18 | 19 | @Service 20 | public class FileServiceImpl implements FileService { 21 | 22 | @Override 23 | public String uploadImage(String path, MultipartFile file) throws IOException { 24 | // File name 25 | String name = file.getOriginalFilename(); 26 | 27 | //validate the image 28 | String fileExtension = name.substring(name.lastIndexOf('.')); 29 | if(!(fileExtension.equals(".png") || fileExtension.equals(".jpg") || fileExtension.equals(".jpeg"))) 30 | { 31 | throw new IncorrectFileFormatException(fileExtension); 32 | } 33 | 34 | // Random name generator file 35 | String randomId = UUID.randomUUID().toString(); 36 | String modifiedFileName = randomId.concat(name.substring(name.lastIndexOf('.'))); 37 | 38 | // Full path 39 | // File.Separator added / and \ slash depending upon OS 40 | String filePath = path + File.separator + modifiedFileName; 41 | 42 | //create folder if not created 43 | File f = new File(path); 44 | if(!f.exists()) { 45 | f.mkdir(); 46 | } 47 | 48 | Files.copy(file.getInputStream(), Paths.get(filePath)); 49 | 50 | return modifiedFileName; 51 | } 52 | 53 | @Override 54 | public InputStream getResources(String path, String fileName) throws FileNotFoundException { 55 | // serving image using rest api 56 | String fullPath = path + File.separator + fileName; 57 | 58 | InputStream is = new FileInputStream(fullPath); 59 | return is; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/impl/PostServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services.impl; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import org.modelmapper.ModelMapper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.data.domain.Sort; 13 | import org.springframework.stereotype.Service; 14 | 15 | import com.lakshy.blog.entities.Category; 16 | import com.lakshy.blog.entities.Post; 17 | import com.lakshy.blog.entities.User; 18 | import com.lakshy.blog.exceptions.ResourceNotFoundException; 19 | import com.lakshy.blog.payloads.PostDto; 20 | import com.lakshy.blog.payloads.PostResponse; 21 | import com.lakshy.blog.repositories.CategoryRepo; 22 | import com.lakshy.blog.repositories.PostRepo; 23 | import com.lakshy.blog.repositories.UserRepo; 24 | import com.lakshy.blog.services.PostService; 25 | 26 | 27 | @Service 28 | public class PostServiceImpl implements PostService { 29 | 30 | @Autowired 31 | private PostRepo postRepo; 32 | 33 | @Autowired 34 | private ModelMapper modelMapper; 35 | 36 | @Autowired 37 | private UserRepo userRepo; 38 | 39 | @Autowired 40 | private CategoryRepo categoryRepo; 41 | 42 | @Override 43 | public PostDto createPost(PostDto postDto, Integer userId, Integer categoryId) { 44 | 45 | User user = this.userRepo.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User", "Id", userId)); 46 | 47 | Category category = this.categoryRepo.findById(categoryId).orElseThrow(() -> new ResourceNotFoundException("Category", "Id", categoryId)); 48 | 49 | Post post = this.modelMapper.map(postDto, Post.class); 50 | post.setImageName("default.png"); 51 | post.setAddedDate(new Date()); 52 | post.setUser(user); 53 | post.setCategory(category); 54 | 55 | Post savedPost = this.postRepo.save(post); 56 | 57 | return this.modelMapper.map(post,PostDto.class); 58 | } 59 | 60 | @Override 61 | public PostDto updatePost(PostDto postDto, Integer postId) { 62 | Post post = this.postRepo.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "Id", postId)); 63 | post.setTitle(postDto.getTitle()); 64 | post.setContent(postDto.getContent()); 65 | post.setImageName(postDto.getImageName()); 66 | 67 | Post updatedPost = postRepo.save(post); 68 | 69 | return this.modelMapper.map(updatedPost, PostDto.class); 70 | } 71 | 72 | @Override 73 | public void deletePost(Integer postId) { 74 | Post post = this.postRepo.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "Id", postId)); 75 | postRepo.delete(post); 76 | 77 | } 78 | 79 | @Override 80 | public List getAllPosts() { 81 | List posts = this.postRepo.findAll(); 82 | List postDtos = posts.stream().map((p) -> this.modelMapper.map(p, PostDto.class)).collect(Collectors.toList()); 83 | return postDtos; 84 | } 85 | 86 | @Override 87 | public PostDto getPostById(Integer postId) { 88 | Post post = this.postRepo.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "Id", postId)); 89 | return this.modelMapper.map(post, PostDto.class); 90 | 91 | } 92 | 93 | @Override 94 | public PostResponse getPostsByCategory(Integer categoryId, Integer pageNumber, Integer pageSize) { 95 | Pageable pg = PageRequest.of(pageNumber, pageSize); 96 | 97 | Category category = this.categoryRepo.findById(categoryId).orElseThrow(() -> new ResourceNotFoundException("Category", "Id", categoryId)); 98 | 99 | Page pagePosts = this.postRepo.findByCategory(category, pg); 100 | List posts = pagePosts.getContent(); 101 | List postDtos = posts.stream().map((post) -> this.modelMapper.map(post, PostDto.class)).collect(Collectors.toList()); 102 | 103 | PostResponse postResponse = new PostResponse(); 104 | postResponse.setContent(postDtos); 105 | postResponse.setPageNumber(pagePosts.getNumber()); 106 | postResponse.setPageSize(pagePosts.getSize()); 107 | postResponse.setTotalElements(pagePosts.getTotalElements()); 108 | postResponse.setTotalPages(pagePosts.getTotalPages()); 109 | postResponse.setLastPage(pagePosts.isLast()); 110 | 111 | return postResponse; 112 | } 113 | 114 | @Override 115 | public List getPostsByUsers(Integer userId) { 116 | User user = this.userRepo.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User", "Id", userId)); 117 | 118 | List posts = this.postRepo.findByUser(user); 119 | // posts.stream().parallel().forEach((s) -> System.out.println(s)); 120 | List postsDtos = posts.stream().map((post) -> this.modelMapper.map(post, PostDto.class)).collect(Collectors.toList()); 121 | 122 | return postsDtos; 123 | } 124 | 125 | @Override 126 | public List searchPost(String keyword) { 127 | List posts = this.postRepo.findByTitleContaining(keyword); 128 | List postDtos = posts.stream().map((post)->this.modelMapper.map(post, PostDto.class)).collect(Collectors.toList()); 129 | return postDtos; 130 | } 131 | 132 | // Pagination 133 | @Override 134 | public PostResponse getAllPostsByPage(Integer pageNumber, Integer pageSize, String sortBy, String sortDir) { 135 | 136 | Sort sort = null; 137 | if(sortDir.equalsIgnoreCase("asc")) { 138 | sort = Sort.by(sortBy).ascending(); 139 | }else { 140 | sort = Sort.by(sortBy).descending(); 141 | } 142 | 143 | Pageable pg = PageRequest.of(pageNumber, pageSize, sort); 144 | 145 | Page pagePosts = this.postRepo.findAll(pg); 146 | List posts = pagePosts.getContent(); 147 | 148 | //content 149 | List postDtos = posts.stream().map((p) -> this.modelMapper.map(p, PostDto.class)).collect(Collectors.toList()); 150 | 151 | PostResponse postResponse = new PostResponse(); 152 | postResponse.setContent(postDtos); 153 | postResponse.setPageNumber(pagePosts.getNumber()); 154 | postResponse.setPageSize(pagePosts.getSize()); 155 | postResponse.setTotalElements(pagePosts.getTotalElements()); 156 | postResponse.setTotalPages(pagePosts.getTotalPages()); 157 | postResponse.setLastPage(pagePosts.isLast()); 158 | 159 | return postResponse; 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/com/lakshy/blog/services/impl/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog.services.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.stream.Collector; 6 | import java.util.stream.Collectors; 7 | 8 | import org.modelmapper.ModelMapper; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.security.crypto.password.PasswordEncoder; 11 | import org.springframework.stereotype.Service; 12 | 13 | import com.lakshy.blog.exceptions.ResourceNotFoundException; 14 | import com.lakshy.blog.config.AppConstants; 15 | import com.lakshy.blog.entities.Role; 16 | import com.lakshy.blog.entities.User; 17 | import com.lakshy.blog.payloads.UserDto; 18 | import com.lakshy.blog.repositories.RoleRepo; 19 | import com.lakshy.blog.repositories.UserRepo; 20 | import com.lakshy.blog.services.UserService; 21 | 22 | @Service 23 | public class UserServiceImpl implements UserService { 24 | 25 | @Autowired 26 | private UserRepo userRepo; 27 | 28 | // we have a bean in main springboot class 29 | @Autowired 30 | private ModelMapper modelMapper; 31 | 32 | @Autowired 33 | private PasswordEncoder passwordEncoder; 34 | 35 | @Autowired 36 | private RoleRepo roleRepo; 37 | 38 | // since we are using UserDto so we need to convert it 39 | // we can also use Modern mapper library instead of these two conversion methods 40 | // convert UserDto to User 41 | private User dtoToUser(UserDto userDto) 42 | { 43 | User user = this.modelMapper.map(userDto, User.class); 44 | /* User user = new User(); 45 | user.setId(userDto.getId()); 46 | user.setName(userDto.getName()); 47 | user.setEmail(userDto.getEmail()); 48 | user.setAbout(userDto.getAbout()); 49 | user.setPassword(userDto.getPassword()); */ 50 | 51 | return user; 52 | } 53 | 54 | // Convert User to UserDto 55 | private UserDto UserToDto(User user) 56 | { 57 | UserDto userDto = this.modelMapper.map(user, UserDto.class); 58 | /*UserDto userDto = new UserDto(); 59 | userDto.setAbout(user.getAbout()); 60 | userDto.setId(user.getId()); 61 | userDto.setName(user.getName()); 62 | userDto.setEmail(user.getEmail()); 63 | userDto.setPassword(user.getPassword());*/ 64 | return userDto; 65 | } 66 | 67 | @Override 68 | public UserDto createUser(UserDto userDto) { 69 | 70 | User user = this.dtoToUser(userDto); 71 | User savedUser = userRepo.save(user); 72 | 73 | return this.UserToDto(savedUser); 74 | } 75 | 76 | @Override 77 | public UserDto updateUser(UserDto userDto, Integer userId) { 78 | User user = userRepo.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User","Id",userId)); 79 | 80 | user.setName(userDto.getName()); 81 | user.setEmail(userDto.getEmail()); 82 | user.setAbout(userDto.getAbout()); 83 | user.setPassword(userDto.getPassword()); 84 | 85 | User updatedUser = userRepo.save(user); 86 | UserDto userDto1 = this.UserToDto(updatedUser); 87 | 88 | return userDto1; 89 | } 90 | 91 | @Override 92 | public UserDto getUserById(Integer userId) { 93 | User user = userRepo.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User","Id",userId)); 94 | UserDto userDto = this.UserToDto(user); 95 | return userDto; 96 | } 97 | 98 | @Override 99 | public List getAllUsers() { 100 | List userList = userRepo.findAll(); 101 | // List userDtoList = new ArrayList<>(); 102 | // for(User u : userList) { 103 | // userDtoList.add(this.UserToDto(u)); 104 | // } 105 | // or 106 | List userDtoList = userList.stream().map(user -> this.UserToDto(user)).collect(Collectors.toList()); 107 | return userDtoList; 108 | } 109 | 110 | @Override 111 | public void deleteUser(Integer userId) { 112 | User user = userRepo.findById(userId).orElseThrow(() -> new ResourceNotFoundException("User","Id",userId)); 113 | userRepo.delete(user); 114 | 115 | } 116 | 117 | @Override 118 | public UserDto registerNewUser(UserDto userDto) { 119 | 120 | User user = this.modelMapper.map(userDto, User.class); 121 | 122 | // encoding the password 123 | user.setPassword(this.passwordEncoder.encode(user.getPassword())); 124 | 125 | // getting role 126 | Role role = this.roleRepo.findById(AppConstants.NORMAL_USER).get(); 127 | 128 | user.getRoles().add(role); 129 | 130 | User savedUser = this.userRepo.save(user); 131 | 132 | return this.modelMapper.map(savedUser, UserDto.class); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9090 2 | 3 | # DB configuration 4 | spring.datasource.url=jdbc:mysql://localhost:3306/blogging_app_apis 5 | spring.datasource.username=root 6 | spring.datasource.password=superadmin123 7 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 8 | 9 | spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect 10 | spring.jpa.hibernate.ddl-auto=update 11 | spring.jpa.show-sql=true 12 | 13 | # fileupload related configuration 14 | spring.servlet.multipart.max-file-size = 10MB 15 | spring.servlet.multipart.max-request-size = 10MB 16 | 17 | project.image = images/ 18 | 19 | # Spring Security 20 | logging.level.org.springframework.security=DEBUG 21 | 22 | # each restart a new password is generated to access the APIs so customize this setting 23 | # == Moved to Database Basic Auth == 24 | # spring.security.user.name=lakshy 25 | # spring.security.user.password=lakshy 26 | # spring.security.user.roles=ADMIN 27 | 28 | spring.devtools.restart.log-condition-evaluation-delta=false -------------------------------------------------------------------------------- /src/test/java/com/lakshy/blog/BloggingAppApisApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.lakshy.blog; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | import com.lakshy.blog.services.UserService; 8 | 9 | @SpringBootTest 10 | class BloggingAppApisApplicationTests { 11 | 12 | @Autowired 13 | private UserService userService; 14 | 15 | @Test 16 | void contextLoads() { 17 | } 18 | 19 | @Test 20 | void serviceTest() { 21 | String className = userService.getClass().getName(); 22 | String packageName = userService.getClass().getPackageName(); 23 | 24 | System.out.println(className); 25 | System.out.println(packageName); 26 | } 27 | 28 | 29 | } 30 | --------------------------------------------------------------------------------