├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── routes-test.http
└── src
├── main
├── java
│ └── com
│ │ └── kvark900
│ │ ├── BootifulApplication.java
│ │ └── api
│ │ ├── configuration
│ │ ├── dataLoaders
│ │ │ ├── TopicsAuthorsBooksLoader.java
│ │ │ └── UsersLoader.java
│ │ └── security
│ │ │ ├── AuthenticationRequest.java
│ │ │ ├── AuthenticationResponse.java
│ │ │ ├── JWTTokenFilter.java
│ │ │ ├── JWTUtil.java
│ │ │ ├── JwtUser.java
│ │ │ ├── JwtUserDetailsService.java
│ │ │ ├── JwtUserFactory.java
│ │ │ ├── WebSecurityConfiguration.java
│ │ │ ├── controller
│ │ │ ├── AuthenticationController.java
│ │ │ └── UserController.java
│ │ │ └── user
│ │ │ ├── Role.java
│ │ │ ├── RoleName.java
│ │ │ ├── RoleRepository.java
│ │ │ ├── RoleService.java
│ │ │ ├── User.java
│ │ │ ├── UserRepository.java
│ │ │ └── UserService.java
│ │ ├── controller
│ │ ├── AuthorController.java
│ │ ├── BookController.java
│ │ └── TopicController.java
│ │ ├── exceptions
│ │ ├── BindingErrorsResponse.java
│ │ ├── Error.java
│ │ └── GlobalControllerExceptionHandler.java
│ │ ├── model
│ │ ├── Author.java
│ │ ├── Book.java
│ │ └── Topic.java
│ │ ├── repository
│ │ ├── AuthorRepository.java
│ │ ├── BookRepository.java
│ │ └── TopicRepository.java
│ │ └── service
│ │ ├── AuthorService.java
│ │ ├── BookService.java
│ │ └── TopicService.java
└── resources
│ ├── application.properties
│ └── banner.txt
└── test
└── java
└── com
└── kvark900
├── BootifulApplicationTests.java
└── TestAuthorController.java
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
25 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kvark900/spring-boot-rest-api-jwt/8ef8cea418602589bdb7e33944f2e80ed080aff3/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spring-boot-rest-api
2 | ###### Bookstore REST API with JWT authentication
3 |
4 | ### Prerequisites
5 | - JDK 1.8+
6 | - Maven
7 |
8 | ### Technology stack:
9 | * Spring Boot
10 | * Spring Data JPA
11 | * MySQL
12 | * Spring Security
13 | * JWT (JSON Web Tokens)
14 |
15 | There are three user accounts :
16 | ```
17 | Admin - admin:admin
18 | User - user:password
19 | Disabled - disabled:password (this user is disabled)
20 | ```
21 |
22 | All endpoints for CRUD operations can be seen in:
23 | - [AuthorController](https://github.com/Kvark900/spring-boot-rest-api/blob/master/src/main/java/com/kvark900/api/controller/AuthorController.java#L23)
24 | - [BookController](https://github.com/Kvark900/spring-boot-rest-api/blob/master/src/main/java/com/kvark900/api/controller/BookController.java#L21)
25 | - [TopicController](https://github.com/Kvark900/spring-boot-rest-api/blob/master/src/main/java/com/kvark900/api/controller/TopicController.java#L18)
26 | ```
27 | /auth - authentication endpoint (HTTP method: POST) - place your credentials in JSON format in request body as JwtAuthenticationRequest
28 |
29 | Use Bearer Token for any listed request:
30 | /authors/** - endpoint for CRUD operations on authors (a valid JWT token must be present in the request header)
31 | /books/** - endpoint for CRUD operations on books (a valid JWT token must be present in the request header)
32 | /topics/** - endpoint for CRUD operations on topics (a valid JWT token must be present in the request header)
33 | ```
34 |
35 |
36 | ### Set up MySQL
37 | Configure database according to [application.properties](https://github.com/Kvark900/spring-boot-rest-api/blob/master/src/main/resources/application.properties) file, or update this file with yours properties.
38 |
--------------------------------------------------------------------------------
/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 | # http://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 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Migwn, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
204 | echo $MAVEN_PROJECTBASEDIR
205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
206 |
207 | # For Cygwin, switch paths to Windows format before running java
208 | if $cygwin; then
209 | [ -n "$M2_HOME" ] &&
210 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
211 | [ -n "$JAVA_HOME" ] &&
212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
213 | [ -n "$CLASSPATH" ] &&
214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
215 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
217 | fi
218 |
219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
220 |
221 | exec "$JAVACMD" \
222 | $MAVEN_OPTS \
223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
226 |
--------------------------------------------------------------------------------
/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 http://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 Maven2 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 key stroke 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 enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
84 | @REM Fallback to current working directory if not found.
85 |
86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
88 |
89 | set EXEC_DIR=%CD%
90 | set WDIR=%EXEC_DIR%
91 | :findBaseDir
92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
93 | cd ..
94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
95 | set WDIR=%CD%
96 | goto findBaseDir
97 |
98 | :baseDirFound
99 | set MAVEN_PROJECTBASEDIR=%WDIR%
100 | cd "%EXEC_DIR%"
101 | goto endDetectBaseDir
102 |
103 | :baseDirNotFound
104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
105 | cd "%EXEC_DIR%"
106 |
107 | :endDetectBaseDir
108 |
109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
110 |
111 | @setlocal EnableExtensions EnableDelayedExpansion
112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
114 |
115 | :endReadAdditionalConfig
116 |
117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
118 |
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
123 | if ERRORLEVEL 1 goto error
124 | goto end
125 |
126 | :error
127 | set ERROR_CODE=1
128 |
129 | :end
130 | @endlocal & set ERROR_CODE=%ERROR_CODE%
131 |
132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
136 | :skipRcPost
137 |
138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
140 |
141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
142 |
143 | exit /B %ERROR_CODE%
144 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.kvark900
7 | bootiful
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 2.1.8.RELEASE
15 |
16 |
17 |
18 | UTF-8
19 | UTF-8
20 | 1.8
21 |
22 |
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-data-jpa
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-web
31 |
32 |
33 |
34 | mysql
35 | mysql-connector-java
36 | runtime
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-test
41 | test
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-starter-tomcat
47 |
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-starter-security
53 | 2.1.8.RELEASE
54 |
55 |
56 |
57 | io.jsonwebtoken
58 | jjwt
59 | 0.9.0
60 |
61 |
62 |
63 |
64 |
65 |
66 | org.springframework.boot
67 | spring-boot-maven-plugin
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/routes-test.http:
--------------------------------------------------------------------------------
1 | ###
2 | GET http://localhost:8080/authors
3 | Content-Type: application/json
4 | Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU4NTQ4MTgzOSwiaWF0IjoxNTg0ODc3MDM5fQ.NwFdUKJqmyAvkLvX5g90Q-d1eL4i3lHOR0oK9SRDjwdI0i5I9G5D3T-P3grvteiCatZ3672Gd9Ojoq5hvJDJPA
5 |
6 | ###
7 | GET http://localhost:8080/books
8 | Content-Type: application/json
9 | Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU4NTQ4MTgzOSwiaWF0IjoxNTg0ODc3MDM5fQ.NwFdUKJqmyAvkLvX5g90Q-d1eL4i3lHOR0oK9SRDjwdI0i5I9G5D3T-P3grvteiCatZ3672Gd9Ojoq5hvJDJPA
10 |
11 |
12 | ### Bad credentials
13 | POST http://localhost:8080/auth
14 | Content-Type: application/json
15 |
16 | {
17 | "username": "admin",
18 | "password": "admin1"
19 | }
20 |
21 | ###
22 |
23 | ### Succces
24 | POST http://localhost:8080/auth
25 | Content-Type: application/json
26 |
27 | {
28 | "username": "admin",
29 | "password": "admin"
30 | }
31 |
32 | ###
33 |
34 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/BootifulApplication.java:
--------------------------------------------------------------------------------
1 | package com.kvark900;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.web.servlet.config.annotation.CorsRegistry;
8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
10 |
11 | @SpringBootApplication
12 | @Configuration
13 | public class BootifulApplication {
14 |
15 | public static void main(String[] args) {
16 | SpringApplication.run(BootifulApplication.class, args);
17 | }
18 |
19 | @Bean
20 | public WebMvcConfigurer corsConfigurer() {
21 | return new WebMvcConfigurerAdapter() {
22 | @Override
23 | public void addCorsMappings(CorsRegistry registry) {
24 | registry.addMapping("/**").allowedOrigins("*");
25 | }
26 | };
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/dataLoaders/TopicsAuthorsBooksLoader.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.dataLoaders;
2 |
3 | import com.kvark900.api.model.Author;
4 | import com.kvark900.api.model.Book;
5 | import com.kvark900.api.model.Topic;
6 | import com.kvark900.api.service.AuthorService;
7 | import com.kvark900.api.service.BookService;
8 | import com.kvark900.api.service.TopicService;
9 | import org.springframework.context.ApplicationListener;
10 | import org.springframework.context.event.ContextRefreshedEvent;
11 | import org.springframework.stereotype.Component;
12 | import org.springframework.transaction.annotation.Transactional;
13 |
14 | import java.math.BigDecimal;
15 | import java.util.HashSet;
16 | import java.util.Set;
17 |
18 | /**
19 | * Created by Keno&Kemo on 03.03.2018..
20 | */
21 | @Component
22 | public class TopicsAuthorsBooksLoader implements ApplicationListener {
23 |
24 | private boolean alreadySetup = false;
25 | private BookService bookService;
26 | private AuthorService authorService;
27 | private TopicService topicService;
28 |
29 |
30 | public TopicsAuthorsBooksLoader(BookService bookService, AuthorService authorService, TopicService topicService) {
31 | this.bookService = bookService;
32 | this.authorService = authorService;
33 | this.topicService = topicService;
34 | }
35 |
36 | // API
37 | @Override
38 | @Transactional
39 | public void onApplicationEvent(final ContextRefreshedEvent event) {
40 | if (alreadySetup) return;
41 |
42 | //region Authors
43 | //===============================================================================
44 | Author gayleLaakmann = createAuthorIfNotFound("Gayle", "Laakmann");
45 | Author richardFeynman = createAuthorIfNotFound("Richard", "Feynman");
46 | Author kathySierra = createAuthorIfNotFound("Kathy", "Sierra");
47 | Author bertBates = createAuthorIfNotFound("Bert", "Bates");
48 | Author thomasCormen = createAuthorIfNotFound("Thomas", "Cormen");
49 | Author charlesLeiserson = createAuthorIfNotFound("Charles","Leiserson");
50 | Author ronaldRivest = createAuthorIfNotFound("Ronald", "Rivest");
51 | //===============================================================================
52 | //endregion
53 |
54 |
55 | //region Topics
56 | //===============================================================================
57 | Topic computerProgramming = createTopicIfNotFound("Computer programming", "Computer programming books");
58 | Topic computersTechnology = createTopicIfNotFound("Computers & Technology", " Books related to Computers & Technology");
59 | Topic carerGuide = createTopicIfNotFound("Carer Guide", "Carer Guide Books");
60 | Topic java = createTopicIfNotFound("Java", "Java Programming Books");
61 | Topic science = createTopicIfNotFound("Science", "Popular science books");
62 | Topic algorithmsDataStructures = createTopicIfNotFound("Data Structures and Algorithms", "Data Structures and Algorithms books");
63 | //===============================================================================
64 | //endregion
65 |
66 |
67 | //region Books
68 |
69 | //region Introduction to Algorithms
70 | //===============================================================================
71 | Set authorsIntroductionToAlgorithms = new HashSet<>();
72 | authorsIntroductionToAlgorithms.add(thomasCormen);
73 | authorsIntroductionToAlgorithms.add(charlesLeiserson);
74 | authorsIntroductionToAlgorithms.add(ronaldRivest);
75 |
76 | Set topicsIntroductionToAlgorithms = new HashSet<>();
77 | topicsIntroductionToAlgorithms.add(algorithmsDataStructures);
78 | topicsIntroductionToAlgorithms.add(computerProgramming);
79 | topicsIntroductionToAlgorithms.add(computersTechnology);
80 |
81 | createBookIfNotFound(
82 | "Introduction to Algorithms", authorsIntroductionToAlgorithms, "2009",
83 | "Introduction to Algorithms, the 'bible' of the field, is a comprehensive textbook " +
84 | "covering the full spectrum of modern algorithms: from the fastest algorithms and data" +
85 | " structures to polynomial-time algorithms for seemingly intractable problems, from " +
86 | "classical algorithms in graph theory to special" +
87 | " algorithms for string matching, computational geometry, and number theory.",
88 | topicsIntroductionToAlgorithms, new BigDecimal(100.17));
89 | //===============================================================================
90 | //endregion
91 |
92 |
93 | //region "Cracking the Coding Interview"
94 | //===============================================================================
95 | Set authorsCrackingTheCodingInterview = new HashSet<>();
96 | authorsCrackingTheCodingInterview.add(gayleLaakmann);
97 |
98 | Set topicsCrackingTheCodingInterview = new HashSet<>();
99 | topicsCrackingTheCodingInterview.add(computerProgramming);
100 | topicsCrackingTheCodingInterview.add(carerGuide);
101 | topicsCrackingTheCodingInterview.add(computersTechnology);
102 |
103 | createBookIfNotFound(
104 | "Cracking the Coding Interview: 150 Programming Questions and Solutions 4th Edtion",
105 | authorsCrackingTheCodingInterview, "2010", "Cracking the Coding Interview " +
106 | "gives you the interview preparation you need to get the top software developer jobs.",
107 | topicsCrackingTheCodingInterview, new BigDecimal(100));
108 |
109 | //===============================================================================
110 | //endregion
111 |
112 |
113 | //region "Head First Java"
114 | //===============================================================================
115 | Set authorsHeadFirstJava = new HashSet<>();
116 | authorsHeadFirstJava.add(kathySierra);
117 | authorsHeadFirstJava.add(bertBates);
118 |
119 | Set topicsHeadFirstJava = new HashSet<>();
120 | topicsHeadFirstJava.add(computerProgramming);
121 | topicsHeadFirstJava.add(computersTechnology);
122 | topicsHeadFirstJava.add(java);
123 |
124 | createBookIfNotFound(
125 | "Head First Java", authorsHeadFirstJava, "2005",
126 | "By exploiting how your brain works, Head First Java compresses the time it takes to " +
127 | "learn and retain--complex information. Its unique approach not only shows you what you " +
128 | "need to know about Java syntax, it teaches you to think like a Java programmer.", topicsHeadFirstJava, new BigDecimal(60));
129 | //===============================================================================
130 | //endregion
131 |
132 |
133 | //region "Surely You're Joking, Mr. Feynman!"
134 | //===============================================================================
135 | Set authorsSurelyYoureJokingMrFeynman = new HashSet<>();
136 | authorsSurelyYoureJokingMrFeynman.add(richardFeynman);
137 |
138 | Set topicsSurelyYoureJokingMrFeynman = new HashSet<>();
139 | topicsSurelyYoureJokingMrFeynman.add(science);
140 |
141 | createBookIfNotFound(
142 | "Surely You're Joking, Mr. Feynman!: Adventures of a Curious Character", authorsSurelyYoureJokingMrFeynman,
143 | "1985", "This is an edited collection of reminiscences by the Nobel Prize-winning" +
144 | " physicist Richard Feynman. The book, released in 1985, covers a variety of instances in Feynman's life.",
145 | topicsSurelyYoureJokingMrFeynman, new BigDecimal(100));
146 | //===============================================================================
147 | //endregion
148 |
149 | //===============================================================================
150 | //endregion
151 |
152 |
153 | alreadySetup = true;
154 | }
155 |
156 | @Transactional
157 | Author createAuthorIfNotFound(String name, String surname) {
158 | Author author = authorService.findByNameAndSurname(name, surname);
159 | if (author == null) {
160 | author = new Author(name, surname);
161 | authorService.save(author);
162 | }
163 | return author;
164 | }
165 |
166 | @Transactional
167 | Topic createTopicIfNotFound(String name, String description) {
168 | Topic topic = topicService.findByName(name);
169 | if (topic == null) {
170 | topic = new Topic(name, description);
171 | topicService.save(topic);
172 | }
173 | return topic;
174 | }
175 |
176 | @Transactional
177 | void createBookIfNotFound(String title, Set authors,
178 | String yearOfPublication, String description,
179 | Set topics, BigDecimal price) {
180 | Book book = bookService.findByTitle(title);
181 | if (book == null) {
182 | book = new Book();
183 | book.setTitle(title);
184 | book.setAuthors(authors);
185 | book.setYearOfPublication(yearOfPublication);
186 | book.setDescription(description);
187 | book.setTopics(topics);
188 | book.setPrice(price);
189 |
190 | bookService.save(book);
191 | }
192 | }
193 |
194 | }
195 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/dataLoaders/UsersLoader.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.dataLoaders;
2 |
3 | import com.kvark900.api.configuration.security.user.*;
4 | import org.springframework.context.ApplicationListener;
5 | import org.springframework.context.event.ContextRefreshedEvent;
6 | import org.springframework.stereotype.Component;
7 | import org.springframework.transaction.annotation.Transactional;
8 |
9 | import java.util.Collections;
10 | import java.util.Date;
11 | import java.util.List;
12 |
13 | @Component
14 | public class UsersLoader implements ApplicationListener {
15 | private boolean dataLoaded = false;
16 | private RoleService roleService;
17 | private UserService userService;
18 |
19 | public UsersLoader(RoleService roleService, UserService userService) {
20 | this.roleService = roleService;
21 | this.userService = userService;
22 | }
23 |
24 | @Override
25 | @Transactional
26 | public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
27 | if (dataLoaded) return;
28 | List adminRoles = Collections.singletonList(createRoleIfNotFound(RoleName.ROLE_ADMIN));
29 | List userRoles = Collections.singletonList(createRoleIfNotFound(RoleName.ROLE_USER));
30 | createUserIfNotFound("admin", "$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi",
31 | "admin", "admin", "admin@admin.com",
32 | true, new Date(1514764800000L), adminRoles);
33 | createUserIfNotFound("user", "$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC",
34 | "user", "user", "enabled@user.com",
35 | true, new Date(1514764800000L), userRoles);
36 | createUserIfNotFound("disabled", "$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC",
37 | "user", "user", "disabled@user.com",
38 | false, new Date(1514764800000L), userRoles);
39 | dataLoaded = true;
40 | }
41 |
42 | @Transactional
43 | Role createRoleIfNotFound(RoleName name) {
44 | if (roleService.roleExists(name)) return null;
45 | Role role = new Role(name);
46 | roleService.save(role);
47 | return role;
48 | }
49 |
50 | @Transactional
51 | void createUserIfNotFound(String userName, String password, String firstName,
52 | String lastName, String email, boolean enabled,
53 | Date lastPasswordResetDate, List roles) {
54 | if (userService.userExists(email)) return;
55 | User user = new User();
56 | user.setUsername(userName);
57 | user.setPassword(password);
58 | user.setFirstname(firstName);
59 | user.setLastname(lastName);
60 | user.setEmail(email);
61 | user.setEnabled(enabled);
62 | user.setLastPasswordResetDate(lastPasswordResetDate);
63 | user.setRoles(roles);
64 | userService.save(user);
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/AuthenticationRequest.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import java.io.Serializable;
4 |
5 |
6 | public class AuthenticationRequest implements Serializable {
7 |
8 | private static final long serialVersionUID = -8445943548965154778L;
9 |
10 | private String username;
11 | private String password;
12 |
13 | public AuthenticationRequest() {
14 | super();
15 | }
16 |
17 | public AuthenticationRequest(String username, String password) {
18 | this.setUsername(username);
19 | this.setPassword(password);
20 | }
21 |
22 | public String getUsername() {
23 | return this.username;
24 | }
25 |
26 | public void setUsername(String username) {
27 | this.username = username;
28 | }
29 |
30 | public String getPassword() {
31 | return this.password;
32 | }
33 |
34 | public void setPassword(String password) {
35 | this.password = password;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/AuthenticationResponse.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import java.io.Serializable;
4 |
5 |
6 | public class AuthenticationResponse implements Serializable {
7 |
8 | private static final long serialVersionUID = 1250166508152483573L;
9 |
10 | private final String token;
11 |
12 | public AuthenticationResponse(String token) {
13 | this.token = token;
14 | }
15 |
16 | public String getToken() {
17 | return this.token;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/JWTTokenFilter.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import io.jsonwebtoken.ExpiredJwtException;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
7 | import org.springframework.security.core.context.SecurityContextHolder;
8 | import org.springframework.security.core.userdetails.UserDetails;
9 | import org.springframework.security.core.userdetails.UserDetailsService;
10 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
11 | import org.springframework.web.filter.OncePerRequestFilter;
12 |
13 | import javax.servlet.FilterChain;
14 | import javax.servlet.ServletException;
15 | import javax.servlet.http.HttpServletRequest;
16 | import javax.servlet.http.HttpServletResponse;
17 | import java.io.IOException;
18 |
19 | public class JWTTokenFilter extends OncePerRequestFilter {
20 |
21 | private final Logger logger = LoggerFactory.getLogger(this.getClass());
22 |
23 | private UserDetailsService userDetailsService;
24 | private JWTUtil jwtUtil;
25 | private String tokenHeader;
26 |
27 | public JWTTokenFilter(UserDetailsService userDetailsService, JWTUtil jwtUtil, String tokenHeader) {
28 | this.userDetailsService = userDetailsService;
29 | this.jwtUtil = jwtUtil;
30 | this.tokenHeader = tokenHeader;
31 | }
32 |
33 | @Override
34 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
35 | authorizeRequest(request);
36 | chain.doFilter(request, response);
37 | }
38 |
39 | private void authorizeRequest(HttpServletRequest request) {
40 | logger.debug("Processing authentication for '{}'", request.getRequestURL());
41 |
42 | final String requestHeader = request.getHeader(this.tokenHeader);
43 |
44 | if (requestHeader == null || !requestHeader.startsWith("Bearer ")) {
45 | logger.warn("Authorization failed. No JWT token found");
46 | return;
47 | }
48 |
49 | String username;
50 | String authToken = requestHeader.substring(7);
51 |
52 | try {
53 | username = jwtUtil.getUsernameFromToken(authToken);
54 | } catch (IllegalArgumentException e) {
55 | logger.error("Error during getting username from token", e);
56 | return;
57 | } catch (ExpiredJwtException e) {
58 | logger.warn("The token has expired", e);
59 | return;
60 | }
61 |
62 | if (username == null || SecurityContextHolder.getContext().getAuthentication() != null) return;
63 |
64 | logger.debug("Security context was null, so authorizing user '{}'...", username);
65 |
66 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
67 |
68 | if (!jwtUtil.validateToken(authToken, userDetails)) {
69 | logger.error("Not a valid token!!!");
70 | return;
71 | }
72 |
73 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
74 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
75 | logger.info("Authorized user '{}', setting security context...", username);
76 | SecurityContextHolder.getContext().setAuthentication(authentication);
77 | }
78 | }
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/JWTUtil.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import io.jsonwebtoken.Claims;
4 | import io.jsonwebtoken.Clock;
5 | import io.jsonwebtoken.Jwts;
6 | import io.jsonwebtoken.SignatureAlgorithm;
7 | import io.jsonwebtoken.impl.DefaultClock;
8 | import org.springframework.beans.factory.annotation.Value;
9 | import org.springframework.security.core.userdetails.UserDetails;
10 | import org.springframework.stereotype.Component;
11 |
12 | import java.io.Serializable;
13 | import java.util.Date;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 | import java.util.function.Function;
17 |
18 | @Component
19 | public class JWTUtil implements Serializable {
20 |
21 | static final String CLAIM_KEY_USERNAME = "sub";
22 | static final String CLAIM_KEY_CREATED = "iat";
23 | private static final long serialVersionUID = -3301605591108950415L;
24 | private Clock clock = DefaultClock.INSTANCE;
25 |
26 | @Value("${jwt.secret}")
27 | private String secret;
28 |
29 | @Value("${jwt.expiration}")
30 | private Long expiration;
31 |
32 | public String getUsernameFromToken(String token) {
33 | return getClaimFromToken(token, Claims::getSubject);
34 | }
35 |
36 | public Date getIssuedAtDateFromToken(String token) {
37 | return getClaimFromToken(token, Claims::getIssuedAt);
38 | }
39 |
40 | public Date getExpirationDateFromToken(String token) {
41 | return getClaimFromToken(token, Claims::getExpiration);
42 | }
43 |
44 | public T getClaimFromToken(String token, Function claimsResolver) {
45 | final Claims claims = getAllClaimsFromToken(token);
46 | return claimsResolver.apply(claims);
47 | }
48 |
49 | private Claims getAllClaimsFromToken(String token) {
50 | return Jwts.parser()
51 | .setSigningKey(secret)
52 | .parseClaimsJws(token)
53 | .getBody();
54 | }
55 |
56 | private Boolean isTokenExpired(String token) {
57 | final Date expiration = getExpirationDateFromToken(token);
58 | return expiration.before(clock.now());
59 | }
60 |
61 | private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
62 | return (lastPasswordReset != null && created.before(lastPasswordReset));
63 | }
64 |
65 | private Boolean ignoreTokenExpiration(String token) {
66 | // here you specify tokens, for that the expiration is ignored
67 | return false;
68 | }
69 |
70 | public String generateToken(UserDetails userDetails) {
71 | Map claims = new HashMap<>();
72 | return doGenerateToken(claims, userDetails.getUsername());
73 | }
74 |
75 | private String doGenerateToken(Map claims, String subject) {
76 | final Date createdDate = clock.now();
77 | final Date expirationDate = calculateExpirationDate(createdDate);
78 |
79 | return Jwts.builder()
80 | .setClaims(claims)
81 | .setSubject(subject)
82 | .setIssuedAt(createdDate)
83 | .setExpiration(expirationDate)
84 | .signWith(SignatureAlgorithm.HS512, secret)
85 | .compact();
86 | }
87 |
88 | public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
89 | final Date created = getIssuedAtDateFromToken(token);
90 | return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
91 | && (!isTokenExpired(token) || ignoreTokenExpiration(token));
92 | }
93 |
94 | public String refreshToken(String token) {
95 | final Date createdDate = clock.now();
96 | final Date expirationDate = calculateExpirationDate(createdDate);
97 |
98 | final Claims claims = getAllClaimsFromToken(token);
99 | claims.setIssuedAt(createdDate);
100 | claims.setExpiration(expirationDate);
101 |
102 | return Jwts.builder()
103 | .setClaims(claims)
104 | .signWith(SignatureAlgorithm.HS512, secret)
105 | .compact();
106 | }
107 |
108 | public Boolean validateToken(String token, UserDetails userDetails) {
109 | JwtUser user = (JwtUser) userDetails;
110 | final String username = getUsernameFromToken(token);
111 | final Date created = getIssuedAtDateFromToken(token);
112 | //final Date expiration = getExpirationDateFromToken(token);
113 | return (
114 | username.equals(user.getUsername())
115 | && !isTokenExpired(token)
116 | && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
117 | );
118 | }
119 |
120 | private Date calculateExpirationDate(Date createdDate) {
121 | return new Date(createdDate.getTime() + expiration * 1000);
122 | }
123 | }
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/JwtUser.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import org.springframework.security.core.GrantedAuthority;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 |
7 | import java.util.Collection;
8 | import java.util.Date;
9 |
10 | public class JwtUser implements UserDetails {
11 |
12 | private final Long id;
13 | private final String username;
14 | private final String firstname;
15 | private final String lastname;
16 | private final String password;
17 | private final String email;
18 | private final Collection extends GrantedAuthority> authorities;
19 | private final boolean enabled;
20 | private final Date lastPasswordResetDate;
21 |
22 | public JwtUser(
23 | Long id,
24 | String username,
25 | String firstname,
26 | String lastname,
27 | String email,
28 | String password, Collection extends GrantedAuthority> authorities,
29 | boolean enabled,
30 | Date lastPasswordResetDate
31 | ) {
32 | this.id = id;
33 | this.username = username;
34 | this.firstname = firstname;
35 | this.lastname = lastname;
36 | this.email = email;
37 | this.password = password;
38 | this.authorities = authorities;
39 | this.enabled = enabled;
40 | this.lastPasswordResetDate = lastPasswordResetDate;
41 | }
42 |
43 | @JsonIgnore
44 | public Long getId() {
45 | return id;
46 | }
47 |
48 | @Override
49 | public String getUsername() {
50 | return username;
51 | }
52 |
53 | @JsonIgnore
54 | @Override
55 | public boolean isAccountNonExpired() {
56 | return true;
57 | }
58 |
59 | @JsonIgnore
60 | @Override
61 | public boolean isAccountNonLocked() {
62 | return true;
63 | }
64 |
65 | @JsonIgnore
66 | @Override
67 | public boolean isCredentialsNonExpired() {
68 | return true;
69 | }
70 |
71 | public String getFirstname() {
72 | return firstname;
73 | }
74 |
75 | public String getLastname() {
76 | return lastname;
77 | }
78 |
79 | public String getEmail() {
80 | return email;
81 | }
82 |
83 | @JsonIgnore
84 | @Override
85 | public String getPassword() {
86 | return password;
87 | }
88 |
89 | @Override
90 | public Collection extends GrantedAuthority> getAuthorities() {
91 | return authorities;
92 | }
93 |
94 | @Override
95 | public boolean isEnabled() {
96 | return enabled;
97 | }
98 |
99 | @JsonIgnore
100 | public Date getLastPasswordResetDate() {
101 | return lastPasswordResetDate;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/JwtUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import com.kvark900.api.configuration.security.user.User;
4 | import com.kvark900.api.configuration.security.user.UserService;
5 | import org.springframework.security.core.userdetails.UserDetails;
6 | import org.springframework.security.core.userdetails.UserDetailsService;
7 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
8 | import org.springframework.stereotype.Service;
9 |
10 | @Service
11 | public class JwtUserDetailsService implements UserDetailsService {
12 |
13 | private UserService userService;
14 |
15 | public JwtUserDetailsService(UserService userService) {
16 | this.userService = userService;
17 | }
18 |
19 | @Override
20 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
21 | User user = userService.findByUsername(username);
22 |
23 | if (user == null) {
24 | throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username));
25 | } else {
26 | return JwtUserFactory.create(user);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/JwtUserFactory.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import com.kvark900.api.configuration.security.user.Role;
4 | import com.kvark900.api.configuration.security.user.User;
5 | import org.springframework.security.core.GrantedAuthority;
6 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
7 |
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | public final class JwtUserFactory {
12 |
13 | private JwtUserFactory() {
14 | }
15 |
16 | public static JwtUser create(User user) {
17 | return new JwtUser(
18 | user.getId(),
19 | user.getUsername(),
20 | user.getFirstname(),
21 | user.getLastname(),
22 | user.getEmail(),
23 | user.getPassword(),
24 | mapToGrantedAuthorities(user.getRoles()),
25 | user.getEnabled(),
26 | user.getLastPasswordResetDate()
27 | );
28 | }
29 |
30 | private static List mapToGrantedAuthorities(List roles) {
31 | return roles.stream()
32 | .map(role -> new SimpleGrantedAuthority(role.getName().name()))
33 | .collect(Collectors.toList());
34 | }
35 | }
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/WebSecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.http.HttpMethod;
8 | import org.springframework.security.authentication.AuthenticationManager;
9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.config.annotation.web.builders.WebSecurity;
13 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
15 | import org.springframework.security.config.http.SessionCreationPolicy;
16 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
17 | import org.springframework.security.crypto.password.PasswordEncoder;
18 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
19 |
20 | @Configuration
21 | @EnableWebSecurity
22 | @EnableGlobalMethodSecurity(prePostEnabled = true)
23 | public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
24 |
25 | private final JWTUtil jwtTokenUtil;
26 | private final JwtUserDetailsService jwtUserDetailsService;
27 |
28 | @Value("${jwt.header}")
29 | private String tokenHeader;
30 |
31 | @Value("${jwt.route.authentication.path}")
32 | private String authenticationPath;
33 |
34 | @Autowired
35 | public WebSecurityConfiguration(JWTUtil jwtUtil, JwtUserDetailsService jwtUserDetailsService) {
36 | this.jwtTokenUtil = jwtUtil;
37 | this.jwtUserDetailsService = jwtUserDetailsService;
38 | }
39 |
40 | @Autowired
41 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
42 | auth
43 | .userDetailsService(jwtUserDetailsService)
44 | .passwordEncoder(passwordEncoderBean());
45 | }
46 |
47 | @Bean
48 | public PasswordEncoder passwordEncoderBean() {
49 | return new BCryptPasswordEncoder();
50 | }
51 |
52 | @Bean
53 | @Override
54 | public AuthenticationManager authenticationManagerBean() throws Exception {
55 | return super.authenticationManagerBean();
56 | }
57 |
58 | @Override
59 | protected void configure(HttpSecurity httpSecurity) throws Exception {
60 | httpSecurity
61 | .csrf().disable()
62 | .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
63 | .authorizeRequests()
64 | .antMatchers("/auth/**").permitAll()
65 | .anyRequest().authenticated();
66 | JWTTokenFilter authenticationTokenFilter = new JWTTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
67 | httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
68 | httpSecurity
69 | .headers()
70 | .frameOptions().sameOrigin()
71 | .cacheControl();
72 | }
73 |
74 | @Override
75 | public void configure(WebSecurity web) throws Exception {
76 | web
77 | .ignoring()
78 | .antMatchers(
79 | HttpMethod.POST,
80 | authenticationPath
81 | )
82 |
83 | // allow anonymous resource requests
84 | .and()
85 | .ignoring()
86 | .antMatchers(
87 | HttpMethod.GET,
88 | "/",
89 | "/*.html",
90 | "/favicon.ico",
91 | "/**/*.html",
92 | "/**/*.css",
93 | "/**/*.js"
94 | );
95 |
96 | }
97 | }
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/controller/AuthenticationController.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.controller;
2 |
3 | import com.kvark900.api.configuration.security.AuthenticationRequest;
4 | import com.kvark900.api.configuration.security.AuthenticationResponse;
5 | import com.kvark900.api.configuration.security.JWTUtil;
6 | import com.kvark900.api.configuration.security.JwtUser;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.beans.factory.annotation.Qualifier;
9 | import org.springframework.beans.factory.annotation.Value;
10 | import org.springframework.http.ResponseEntity;
11 | import org.springframework.security.authentication.AuthenticationManager;
12 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13 | import org.springframework.security.core.userdetails.UserDetails;
14 | import org.springframework.security.core.userdetails.UserDetailsService;
15 | import org.springframework.web.bind.annotation.RequestBody;
16 | import org.springframework.web.bind.annotation.RequestMapping;
17 | import org.springframework.web.bind.annotation.RequestMethod;
18 | import org.springframework.web.bind.annotation.RestController;
19 |
20 | import javax.servlet.http.HttpServletRequest;
21 |
22 | @RestController
23 | public class AuthenticationController {
24 |
25 | @Value("${jwt.header}")
26 | private String tokenHeader;
27 | private final AuthenticationManager authenticationManager;
28 | private final JWTUtil jwtUtil;
29 | private final UserDetailsService userDetailsService;
30 |
31 | @Autowired
32 | public AuthenticationController(AuthenticationManager authenticationManager, JWTUtil jwtUtil,
33 | @Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService) {
34 | this.authenticationManager = authenticationManager;
35 | this.jwtUtil = jwtUtil;
36 | this.userDetailsService = userDetailsService;
37 | }
38 |
39 | @RequestMapping(value = "${jwt.route.authentication.path}", method = RequestMethod.POST)
40 | public ResponseEntity> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) {
41 | authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
42 | final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
43 | final String token = jwtUtil.generateToken(userDetails);
44 | return ResponseEntity.ok(new AuthenticationResponse(token));
45 | }
46 |
47 | @RequestMapping(value = "${jwt.route.authentication.refresh}", method = RequestMethod.GET)
48 | public ResponseEntity> refreshAndGetAuthenticationToken(HttpServletRequest request) {
49 | String authToken = request.getHeader(tokenHeader);
50 | final String token = authToken.substring(7);
51 | String username = jwtUtil.getUsernameFromToken(token);
52 | JwtUser user = (JwtUser) userDetailsService.loadUserByUsername(username);
53 |
54 | if (jwtUtil.canTokenBeRefreshed(token, user.getLastPasswordResetDate())) {
55 | String refreshedToken = jwtUtil.refreshToken(token);
56 | return ResponseEntity.ok(new AuthenticationResponse(refreshedToken));
57 | } else {
58 | return ResponseEntity.badRequest().body(null);
59 | }
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.controller;
2 |
3 | import com.kvark900.api.configuration.security.JWTUtil;
4 | import com.kvark900.api.configuration.security.JwtUser;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.beans.factory.annotation.Qualifier;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.security.core.userdetails.UserDetailsService;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RequestMethod;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | import javax.servlet.http.HttpServletRequest;
14 |
15 | @RestController
16 | public class UserController {
17 |
18 | @Value("${jwt.header}")
19 | private String tokenHeader;
20 |
21 | @Autowired
22 | private JWTUtil jwtUtil;
23 |
24 | @Autowired
25 | @Qualifier("jwtUserDetailsService")
26 | private UserDetailsService userDetailsService;
27 |
28 | @RequestMapping(value = "user", method = RequestMethod.GET)
29 | public JwtUser getAuthenticatedUser(HttpServletRequest request) {
30 | String token = request.getHeader(tokenHeader).substring(7);
31 | String username = jwtUtil.getUsernameFromToken(token);
32 | return (JwtUser) userDetailsService.loadUserByUsername(username);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/Role.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | import com.fasterxml.jackson.annotation.JsonBackReference;
4 |
5 | import javax.persistence.*;
6 | import javax.validation.constraints.NotNull;
7 | import java.util.List;
8 |
9 | @Entity
10 | public class Role {
11 | @Id
12 | @Column(name = "ID")
13 | @GeneratedValue(strategy = GenerationType.AUTO)
14 | private Long id;
15 |
16 | @Column(name = "NAME", length = 50, unique = true)
17 | @NotNull
18 | @Enumerated(EnumType.STRING)
19 | private RoleName name;
20 |
21 | @JsonBackReference
22 | @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
23 | private List users;
24 |
25 | public Role() {
26 | }
27 |
28 | public Role(final RoleName name) {
29 | this.name = name;
30 | }
31 |
32 | public Role(RoleName name, List users) {
33 | this.name = name;
34 | this.users = users;
35 | }
36 |
37 | public Long getId() {
38 | return id;
39 | }
40 |
41 | public void setId(Long id) {
42 | this.id = id;
43 | }
44 |
45 | public RoleName getName() {
46 | return name;
47 | }
48 |
49 | public void setName(RoleName name) {
50 | this.name = name;
51 | }
52 |
53 | public List getUsers() {
54 | return users;
55 | }
56 |
57 | public void setUsers(List users) {
58 | this.users = users;
59 | }
60 |
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/RoleName.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | public enum RoleName {
4 | ROLE_USER,
5 | ROLE_ADMIN
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/RoleRepository.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | import java.util.Optional;
6 |
7 | public interface RoleRepository extends JpaRepository {
8 |
9 | boolean existsRoleByName(RoleName name);
10 | Role findByName(RoleName name);
11 | Optional findById(Long id);
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/RoleService.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | import java.util.List;
6 | import java.util.Optional;
7 |
8 | @Service
9 | public class RoleService {
10 | private RoleRepository roleRepository;
11 |
12 | public RoleService(RoleRepository roleRepository) {
13 | this.roleRepository = roleRepository;
14 | }
15 |
16 | public List findAll() {
17 | return roleRepository.findAll();
18 | }
19 |
20 | public Role findByName(RoleName name) {
21 | return roleRepository.findByName(name);
22 | }
23 |
24 | public boolean roleExists(RoleName name) {
25 | return roleRepository.existsRoleByName(name);
26 | }
27 |
28 | public Optional findById(Long id) {
29 | return roleRepository.findById(id);
30 | }
31 |
32 | public void save(Role role) {
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/User.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | import javax.persistence.*;
4 | import javax.validation.constraints.NotNull;
5 | import javax.validation.constraints.Size;
6 | import java.util.Date;
7 | import java.util.List;
8 |
9 | @Entity
10 | public class User {
11 | @Id
12 | @Column(name = "ID")
13 | @GeneratedValue(strategy = GenerationType.AUTO)
14 | private Long id;
15 |
16 | @Column(name = "USERNAME", length = 50, unique = true)
17 | @NotNull
18 | @Size(min = 4, max = 50)
19 | private String username;
20 |
21 | @Column(name = "PASSWORD", length = 100)
22 | @NotNull
23 | @Size(min = 4, max = 100)
24 | private String password;
25 |
26 | @Column(name = "FIRSTNAME", length = 50)
27 | @NotNull
28 | @Size(min = 4, max = 50)
29 | private String firstname;
30 |
31 | @Column(name = "LASTNAME", length = 50)
32 | @NotNull
33 | @Size(min = 4, max = 50)
34 | private String lastname;
35 |
36 | @Column(name = "EMAIL", length = 50)
37 | @NotNull
38 | @Size(min = 4, max = 50)
39 | private String email;
40 |
41 | @Column(name = "ENABLED")
42 | @NotNull
43 | private Boolean enabled;
44 |
45 | @Column(name = "LAST_PASSWORD_RESET_DATE")
46 | @Temporal(TemporalType.TIMESTAMP)
47 | @NotNull
48 | private Date lastPasswordResetDate;
49 |
50 | @NotNull
51 | @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
52 | @JoinTable(
53 | name = "USER_ROLE",
54 | joinColumns = {@JoinColumn(name = "USER_ID")},
55 | inverseJoinColumns = {@JoinColumn(name = "ROLE_ID")})
56 | private List roles;
57 |
58 | public User() {
59 | }
60 |
61 | public User(String username, String password, String firstname, String lastname, String email, Boolean enabled,
62 | Date lastPasswordResetDate, List roles) {
63 | this.username = username;
64 | this.password = password;
65 | this.firstname = firstname;
66 | this.lastname = lastname;
67 | this.email = email;
68 | this.enabled = enabled;
69 | this.lastPasswordResetDate = lastPasswordResetDate;
70 | this.roles = roles;
71 | }
72 |
73 | public Long getId() {
74 | return id;
75 | }
76 |
77 | public void setId(Long id) {
78 | this.id = id;
79 | }
80 |
81 | public String getUsername() {
82 | return username;
83 | }
84 |
85 | public void setUsername(String username) {
86 | this.username = username;
87 | }
88 |
89 | public String getPassword() {
90 | return password;
91 | }
92 |
93 | public void setPassword(String password) {
94 | this.password = password;
95 | }
96 |
97 | public String getFirstname() {
98 | return firstname;
99 | }
100 |
101 | public void setFirstname(String firstname) {
102 | this.firstname = firstname;
103 | }
104 |
105 | public String getLastname() {
106 | return lastname;
107 | }
108 |
109 | public void setLastname(String lastname) {
110 | this.lastname = lastname;
111 | }
112 |
113 | public String getEmail() {
114 | return email;
115 | }
116 |
117 | public void setEmail(String email) {
118 | this.email = email;
119 | }
120 |
121 | public boolean getEnabled() {
122 | return enabled;
123 | }
124 |
125 | public void setEnabled(boolean enabled) {
126 | this.enabled = enabled;
127 | }
128 |
129 | public Date getLastPasswordResetDate() {
130 | return lastPasswordResetDate;
131 | }
132 |
133 | public void setLastPasswordResetDate(Date lastPasswordResetDate) {
134 | this.lastPasswordResetDate = lastPasswordResetDate;
135 | }
136 |
137 | public List getRoles() {
138 | return roles;
139 | }
140 |
141 | public void setRoles(List roles) {
142 | this.roles = roles;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 | import org.springframework.stereotype.Repository;
5 |
6 | @Repository
7 | public interface UserRepository extends JpaRepository {
8 |
9 | User findByUsername(String username);
10 | boolean existsUsersByEmail(String email);
11 |
12 | User findByEmail(String email);
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/configuration/security/user/UserService.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.configuration.security.user;
2 |
3 | import org.springframework.stereotype.Service;
4 |
5 | @Service
6 | public class UserService {
7 | private UserRepository userRepository;
8 |
9 | public UserService(UserRepository userRepository) {
10 | this.userRepository = userRepository;
11 | }
12 |
13 | public User findByUsername(String username) {
14 | return userRepository.findByUsername(username);
15 | }
16 |
17 | public User findByEmail(String email) {
18 | return userRepository.findByEmail(email);
19 | }
20 |
21 | public void save(User user) {
22 | userRepository.save(user);
23 | }
24 |
25 | public boolean userExists(String email) {
26 | return userRepository.existsUsersByEmail(email);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/controller/AuthorController.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.controller;
2 |
3 | import com.kvark900.api.model.Author;
4 | import com.kvark900.api.exceptions.BindingErrorsResponse;
5 | import com.kvark900.api.service.AuthorService;
6 | import org.springframework.http.HttpHeaders;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.validation.BindingResult;
11 | import org.springframework.web.bind.annotation.*;
12 | import org.springframework.web.util.UriComponentsBuilder;
13 |
14 | import javax.validation.Valid;
15 | import java.util.List;
16 | import java.util.Optional;
17 |
18 | /**
19 | * Created by Keno&Kemo on 17.12.2017..
20 | */
21 | @SuppressWarnings("Duplicates")
22 | @RestController
23 | @RequestMapping(value = "/authors", produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
24 | consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
25 | public class AuthorController {
26 | private AuthorService authorService;
27 |
28 | public AuthorController(AuthorService authorService) {
29 | this.authorService = authorService;
30 | }
31 |
32 | @GetMapping("")
33 | public ResponseEntity> getAllAuthors() {
34 | List allAuthors = authorService.findAll();
35 | if (allAuthors == null)
36 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
37 | else if (allAuthors.isEmpty())
38 | return new ResponseEntity<>(allAuthors, HttpStatus.NO_CONTENT);
39 | else return new ResponseEntity<>(allAuthors, HttpStatus.OK);
40 | }
41 |
42 | @GetMapping("/{id}")
43 | public ResponseEntity getAuthor(@PathVariable Long id) {
44 | return authorService
45 | .findById(id)
46 | .map(author -> new ResponseEntity<>(author, HttpStatus.OK))
47 | .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
48 | }
49 |
50 | @PostMapping("")
51 | public ResponseEntity saveAuthor(@RequestBody @Valid Author author, BindingResult bindingResult,
52 | UriComponentsBuilder uriComponentsBuilder) {
53 | BindingErrorsResponse errors = new BindingErrorsResponse();
54 | HttpHeaders headers = new HttpHeaders();
55 | if (bindingResult.hasErrors() || (author == null)) {
56 | errors.addAllErrors(bindingResult);
57 | headers.add("errors", errors.toJSON());
58 | return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST);
59 | }
60 | authorService.save(author);
61 | headers.setLocation(uriComponentsBuilder.path("/authors/{id}").buildAndExpand(author.getId()).toUri());
62 | return new ResponseEntity<>(author, headers, HttpStatus.CREATED);
63 | }
64 |
65 | @PutMapping("/{id}")
66 | public ResponseEntity updateAuthor(@PathVariable("id") Long id, @RequestBody @Valid Author author,
67 | BindingResult bindingResult) {
68 | Optional currentAuthor = authorService.findById(id);
69 | BindingErrorsResponse errors = new BindingErrorsResponse();
70 | HttpHeaders headers = new HttpHeaders();
71 | if (bindingResult.hasErrors() || (author == null)) {
72 | errors.addAllErrors(bindingResult);
73 | headers.add("errors", errors.toJSON());
74 | return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST);
75 | }
76 | if (!currentAuthor.isPresent())
77 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
78 |
79 | authorService.update(author);
80 | return new ResponseEntity<>(author, HttpStatus.NO_CONTENT);
81 | }
82 |
83 | @DeleteMapping("/{id}")
84 | public ResponseEntity deleteAuthor(@PathVariable("id") Long id) {
85 | Optional authorToDelete = authorService.findById(id);
86 | if (!authorToDelete.isPresent())
87 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
88 |
89 | authorService.delete(id);
90 | return new ResponseEntity<>(authorToDelete.get(), HttpStatus.NO_CONTENT);
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/controller/BookController.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.controller;
2 |
3 | import com.kvark900.api.exceptions.BindingErrorsResponse;
4 | import com.kvark900.api.model.Book;
5 | import com.kvark900.api.service.BookService;
6 | import org.springframework.http.HttpHeaders;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.http.MediaType;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.validation.BindingResult;
11 | import org.springframework.web.bind.annotation.*;
12 | import org.springframework.web.util.UriComponentsBuilder;
13 |
14 | import javax.validation.Valid;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 | import java.util.Optional;
18 |
19 | @SuppressWarnings("Duplicates")
20 | @RestController
21 | @RequestMapping(value = "/books", produces = MediaType.APPLICATION_JSON_UTF8_VALUE,
22 | consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
23 | public class BookController {
24 | private BookService bookService;
25 |
26 | public BookController(BookService bookService) {
27 | this.bookService = bookService;
28 | }
29 |
30 | @GetMapping("")
31 | public ResponseEntity> getAllBooks() {
32 | List allBooks = bookService.findAll();
33 | if (allBooks == null)
34 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
35 | else if (allBooks.isEmpty())
36 | return new ResponseEntity<>(allBooks, HttpStatus.NO_CONTENT);
37 | else
38 | return new ResponseEntity<>(allBooks, HttpStatus.OK);
39 | }
40 |
41 | @GetMapping("/{id}")
42 | public ResponseEntity getBook(@PathVariable Long id) {
43 | return bookService.findById(id)
44 | .map(book -> new ResponseEntity<>(book, HttpStatus.OK))
45 | .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
46 | }
47 |
48 | @GetMapping("/by-topic-id/{topicIds}")
49 | public ResponseEntity> getAllBooksByTopicId(@PathVariable Long[] topicIds) {
50 | List allBooksByTopicId = new ArrayList<>();
51 | for (Long topicId : topicIds) {
52 | List booksByTopicId = bookService.findByTopicsId(topicId);
53 | if (!booksByTopicId.isEmpty())
54 | allBooksByTopicId.addAll(booksByTopicId);
55 | }
56 | if (allBooksByTopicId.isEmpty())
57 | return new ResponseEntity<>(allBooksByTopicId, HttpStatus.NO_CONTENT);
58 | return new ResponseEntity<>(allBooksByTopicId, HttpStatus.OK);
59 | }
60 |
61 | @GetMapping("/by-author-id/{authorIds}")
62 | public ResponseEntity> getAllBooksByAuthorId(@PathVariable Long[] authorIds) {
63 | List allBooksByAuthorId = new ArrayList<>();
64 | for (Long authorId : authorIds) {
65 | List booksByAuthorId = bookService.findByAuthorsId(authorId);
66 | if (!booksByAuthorId.isEmpty()) allBooksByAuthorId.addAll(booksByAuthorId);
67 | }
68 | if (allBooksByAuthorId.isEmpty())
69 | return new ResponseEntity<>(allBooksByAuthorId, HttpStatus.NO_CONTENT);
70 |
71 | return new ResponseEntity<>(allBooksByAuthorId, HttpStatus.OK);
72 | }
73 |
74 | @PostMapping("")
75 | public ResponseEntity saveBook(@RequestBody @Valid Book book, BindingResult bindingResult,
76 | UriComponentsBuilder uriComponentsBuilder) {
77 | BindingErrorsResponse errors = new BindingErrorsResponse();
78 | HttpHeaders headers = new HttpHeaders();
79 | if (bindingResult.hasErrors() || (book == null)) {
80 | errors.addAllErrors(bindingResult);
81 | headers.add("errors", errors.toJSON());
82 | return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST);
83 | }
84 |
85 | bookService.save(book);
86 | headers.setLocation(uriComponentsBuilder.path("/books/{id}").buildAndExpand(book.getId()).toUri());
87 | return new ResponseEntity<>(book, headers, HttpStatus.CREATED);
88 |
89 | }
90 |
91 | @PutMapping("/{id}")
92 | public ResponseEntity updateBook(@PathVariable("id") Long id, @RequestBody @Valid Book book,
93 | BindingResult bindingResult) {
94 | Optional currentBook = bookService.findById(id);
95 | BindingErrorsResponse errors = new BindingErrorsResponse();
96 | HttpHeaders headers = new HttpHeaders();
97 | if (bindingResult.hasErrors() || (book == null)) {
98 | errors.addAllErrors(bindingResult);
99 | headers.add("errors", errors.toJSON());
100 | return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST);
101 | }
102 | if (!currentBook.isPresent())
103 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
104 |
105 | bookService.update(book);
106 | return new ResponseEntity<>(book, HttpStatus.NO_CONTENT);
107 | }
108 |
109 | @DeleteMapping("/{id}")
110 | public ResponseEntity deleteBook(@PathVariable("id") Long id) {
111 | Optional bookToDelete = bookService.findById(id);
112 | if (!bookToDelete.isPresent())
113 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
114 | bookService.delete(id);
115 | return new ResponseEntity<>(bookToDelete.get(), HttpStatus.NO_CONTENT);
116 |
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/controller/TopicController.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.controller;
2 |
3 | import com.kvark900.api.exceptions.BindingErrorsResponse;
4 | import com.kvark900.api.model.Topic;
5 | import com.kvark900.api.service.TopicService;
6 | import org.springframework.http.HttpHeaders;
7 | import org.springframework.http.HttpStatus;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.validation.BindingResult;
10 | import org.springframework.web.bind.annotation.*;
11 | import org.springframework.web.util.UriComponentsBuilder;
12 |
13 | import javax.validation.Valid;
14 | import java.util.List;
15 | import java.util.Optional;
16 |
17 | @SuppressWarnings("Duplicates")
18 | @RestController
19 | @RequestMapping(value = "/topics")
20 | public class TopicController {
21 | private final TopicService topicService;
22 |
23 | public TopicController(TopicService topicService) {
24 | this.topicService = topicService;
25 | }
26 |
27 |
28 | @GetMapping("")
29 | public ResponseEntity> getAllTopics() {
30 | List allTopics = topicService.findAll();
31 | if (allTopics == null)
32 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
33 | else if (allTopics.isEmpty())
34 | return new ResponseEntity<>(allTopics, HttpStatus.NO_CONTENT);
35 | return new ResponseEntity<>(allTopics, HttpStatus.OK);
36 | }
37 |
38 | @GetMapping("/{id}")
39 | public ResponseEntity getTopic(@PathVariable Long id) {
40 | return topicService
41 | .findById(id)
42 | .map(topic -> new ResponseEntity<>(topic, HttpStatus.OK))
43 | .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
44 | }
45 |
46 | @PostMapping("")
47 | public ResponseEntity addTopic(@RequestBody @Valid Topic topic, BindingResult bindingResult,
48 | UriComponentsBuilder uriComponentsBuilder) {
49 | BindingErrorsResponse errors = new BindingErrorsResponse();
50 | HttpHeaders headers = new HttpHeaders();
51 | if (bindingResult.hasErrors() || (topic == null)) {
52 | errors.addAllErrors(bindingResult);
53 | headers.add("errors", errors.toJSON());
54 | return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST);
55 | }
56 | topicService.save(topic);
57 | headers.setLocation(uriComponentsBuilder.path("/books/{id}").buildAndExpand(topic.getId()).toUri());
58 | return new ResponseEntity<>(topic, headers, HttpStatus.CREATED);
59 |
60 | }
61 |
62 | @DeleteMapping("/{id}")
63 | public ResponseEntity deleteTopic(@PathVariable Long id) {
64 | Optional topicToDelete = topicService.findById(id);
65 | if (!topicToDelete.isPresent())
66 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
67 | topicService.delete(id);
68 | return new ResponseEntity<>(topicToDelete.get(), HttpStatus.NO_CONTENT);
69 | }
70 |
71 | @PutMapping("/{id}")
72 | public ResponseEntity updateTopic(@PathVariable Long id, @RequestBody @Valid Topic topic,
73 | BindingResult bindingResult) {
74 | Optional currentTopic = topicService.findById(id);
75 | BindingErrorsResponse errors = new BindingErrorsResponse();
76 | HttpHeaders headers = new HttpHeaders();
77 | if (bindingResult.hasErrors() || (topic == null)) {
78 | errors.addAllErrors(bindingResult);
79 | headers.add("errors", errors.toJSON());
80 | return new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST);
81 | }
82 | if (!currentTopic.isPresent())
83 | return new ResponseEntity<>(HttpStatus.NOT_FOUND);
84 | topicService.update(topic);
85 | return new ResponseEntity<>(topic, HttpStatus.NO_CONTENT);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/exceptions/BindingErrorsResponse.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.exceptions;
2 |
3 | import com.fasterxml.jackson.annotation.JsonAutoDetect;
4 | import com.fasterxml.jackson.annotation.PropertyAccessor;
5 | import com.fasterxml.jackson.core.JsonProcessingException;
6 | import com.fasterxml.jackson.databind.ObjectMapper;
7 | import org.springframework.validation.BindingResult;
8 | import org.springframework.validation.FieldError;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by Keno&Kemo on 23.12.2017..
15 | */
16 |
17 | public class BindingErrorsResponse {
18 | private List bindingErrors = new ArrayList<>();
19 |
20 | public void addAllErrors(BindingResult bindingResult) {
21 | for (FieldError fieldError : bindingResult.getFieldErrors()) {
22 | BindingError error = new BindingError();
23 | error.setObjectName(fieldError.getObjectName());
24 | error.setFieldName(fieldError.getField());
25 | error.setFieldValue(String.valueOf(fieldError.getRejectedValue()));
26 | error.setErrorMessage(fieldError.getDefaultMessage());
27 | addError(error);
28 | }
29 | }
30 |
31 | public String toJSON() {
32 | ObjectMapper mapper = new ObjectMapper();
33 | mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
34 | String errorsAsJSON = "";
35 | try {
36 | errorsAsJSON = mapper.writeValueAsString(bindingErrors);
37 | } catch (JsonProcessingException e) {
38 | e.printStackTrace();
39 | }
40 | return errorsAsJSON;
41 | }
42 |
43 | public List getBindingErrors() {
44 | return bindingErrors;
45 | }
46 |
47 | public void setBindingErrors(List bindingErrors) {
48 | this.bindingErrors = bindingErrors;
49 | }
50 |
51 | public void addError(BindingError bindingError) {
52 | this.bindingErrors.add(bindingError);
53 | }
54 |
55 |
56 | @Override
57 | public String toString() {
58 | return "BindingErrorsResponse [bindingErrors=" + bindingErrors + "]";
59 | }
60 |
61 | protected class BindingError {
62 |
63 | private String objectName;
64 | private String fieldName;
65 | private String fieldValue;
66 | private String errorMessage;
67 |
68 | public BindingError() {
69 | this.objectName = "";
70 | this.fieldName = "";
71 | this.fieldValue = "";
72 | this.errorMessage = "";
73 | }
74 |
75 | protected String getObjectName() {
76 | return objectName;
77 | }
78 |
79 | protected void setObjectName(String objectName) {
80 | this.objectName = objectName;
81 | }
82 |
83 | protected String getFieldName() {
84 | return fieldName;
85 | }
86 |
87 | protected void setFieldName(String fieldName) {
88 | this.fieldName = fieldName;
89 | }
90 |
91 | protected String getFieldValue() {
92 | return fieldValue;
93 | }
94 |
95 | protected void setFieldValue(String fieldValue) {
96 | this.fieldValue = fieldValue;
97 | }
98 |
99 | protected String getErrorMessage() {
100 | return errorMessage;
101 | }
102 |
103 | protected void setErrorMessage(String error_message) {
104 | this.errorMessage = error_message;
105 | }
106 |
107 | @Override
108 | public String toString() {
109 | return "BindingError [objectName=" + objectName + ", fieldName=" + fieldName + ", fieldValue=" + fieldValue
110 | + ", errorMessage=" + errorMessage + "]";
111 | }
112 |
113 | }
114 |
115 | }
116 |
117 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/exceptions/Error.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.exceptions;
2 |
3 | import com.fasterxml.jackson.annotation.JsonFormat;
4 |
5 | import java.util.Date;
6 |
7 | public class Error {
8 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd.MM.yyyy. HH:mm a z")
9 | private Date timestamp;
10 | private String status;
11 | private String error;
12 | private String path;
13 |
14 | public Error(Date timestamp, String error, String path) {
15 | this.timestamp = timestamp;
16 | this.error = error;
17 | this.path = path;
18 | }
19 |
20 | public Error status(String httpStatus) {
21 | setStatus(httpStatus);
22 | return this;
23 | }
24 |
25 | public Date getTimestamp() {
26 | return timestamp;
27 | }
28 |
29 | public void setTimestamp(Date timestamp) {
30 | this.timestamp = timestamp;
31 | }
32 |
33 | public String getStatus() {
34 | return status;
35 | }
36 |
37 | public void setStatus(String status) {
38 | this.status = status;
39 | }
40 |
41 | public String getError() {
42 | return error;
43 | }
44 |
45 | public void setError(String error) {
46 | this.error = error;
47 | }
48 |
49 | public String getPath() {
50 | return path;
51 | }
52 |
53 | public void setPath(String path) {
54 | this.path = path;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/exceptions/GlobalControllerExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.exceptions;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.http.ResponseEntity;
5 | import org.springframework.security.authentication.BadCredentialsException;
6 | import org.springframework.security.authentication.DisabledException;
7 | import org.springframework.security.core.AuthenticationException;
8 | import org.springframework.web.bind.annotation.ControllerAdvice;
9 | import org.springframework.web.bind.annotation.ExceptionHandler;
10 |
11 | import javax.servlet.http.HttpServletRequest;
12 | import java.util.Date;
13 |
14 | @ControllerAdvice
15 | class GlobalControllerExceptionHandler {
16 | @ExceptionHandler(value = AuthenticationException.class)
17 | public ResponseEntity handleAuthenticationException(HttpServletRequest request, AuthenticationException e) {
18 | Error error = new Error(new Date(), e.getMessage(), request.getRequestURI());
19 | if (e instanceof BadCredentialsException)
20 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error.status(HttpStatus.BAD_REQUEST.toString()));
21 | else if (e instanceof DisabledException)
22 | return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error.status(HttpStatus.FORBIDDEN.toString()));
23 | return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error.status(HttpStatus.UNAUTHORIZED.toString()));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/model/Author.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonBackReference;
4 |
5 | import javax.persistence.*;
6 | import javax.validation.constraints.NotNull;
7 | import java.util.HashSet;
8 | import java.util.Set;
9 |
10 | /**
11 | * Created by Keno&Kemo on 17.12.2017..
12 | */
13 | @Entity
14 | public class Author {
15 | @Id
16 | @GeneratedValue
17 | private Long id;
18 | @NotNull
19 | private String name;
20 | @NotNull
21 | private String surname;
22 |
23 | @JsonBackReference
24 | // @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
25 | @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "authors")
26 | private Set books = new HashSet<>();
27 |
28 |
29 | //Constructors
30 | public Author(String name, String surname, Set books) {
31 | this.id = id;
32 | this.name = name;
33 | this.surname = surname;
34 | this.books = books;
35 | }
36 |
37 | public Author(String name, String surname) {
38 | this.id = id;
39 | this.name = name;
40 | this.surname = surname;
41 | }
42 |
43 | public Author() {
44 | }
45 |
46 | //getters and setters
47 | public String getName() {
48 | return name;
49 | }
50 |
51 | public void setName(String name) {
52 | this.name = name;
53 | }
54 |
55 | public String getSurname() {
56 | return surname;
57 | }
58 |
59 | public void setSurname(String surname) {
60 | this.surname = surname;
61 | }
62 |
63 | public Set getBooks() {
64 | return books;
65 | }
66 |
67 | public void setBooks(Set books) {
68 | this.books = books;
69 | }
70 |
71 | public Long getId() {
72 | return id;
73 | }
74 |
75 | public void setId(Long id) {
76 | this.id = id;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/model/Book.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonManagedReference;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 |
6 | import javax.persistence.*;
7 | import javax.validation.constraints.NotNull;
8 | import java.io.Serializable;
9 | import java.math.BigDecimal;
10 | import java.util.HashSet;
11 | import java.util.Set;
12 |
13 | @Entity
14 | public class Book implements Serializable {
15 | @Id
16 | @GeneratedValue
17 | private Long id;
18 | @NotNull
19 | private String title;
20 |
21 | @JsonManagedReference
22 | @NotNull
23 | @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
24 | @JoinTable(name = "book_author",
25 | joinColumns = @JoinColumn(name = "book_id"),
26 | inverseJoinColumns = @JoinColumn(name = "author_id"))
27 | private Set authors = new HashSet<>();
28 |
29 | private String yearOfPublication;
30 |
31 | @Lob
32 | private String description;
33 |
34 | @JsonManagedReference
35 | // @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
36 | @NotNull
37 | @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST, targetEntity = Topic.class)
38 | @JoinTable(name = "book_topic",
39 | joinColumns = @JoinColumn(name = "book_id"),
40 | inverseJoinColumns = @JoinColumn(name = "topic_id"))
41 | private Set topics = new HashSet<>();
42 |
43 | @NotNull
44 | private BigDecimal price;
45 |
46 | //Constructors
47 | @Autowired
48 | public Book(Long id, String title, Set author, String yearOfPublication,
49 | String description, Set topics, BigDecimal price) {
50 | this.id = id;
51 | this.title = title;
52 | this.authors = author;
53 | this.yearOfPublication = yearOfPublication;
54 | this.description = description;
55 | this.topics = topics;
56 | this.price = price;
57 | }
58 |
59 | public Book() {
60 | }
61 |
62 | //getters and setters
63 | public Long getId() {
64 | return id;
65 | }
66 |
67 | public void setId(Long id) {
68 | this.id = id;
69 | }
70 |
71 | public String getDescription() {
72 | return description;
73 | }
74 |
75 | public void setDescription(String description) {
76 | this.description = description;
77 | }
78 |
79 | public String getTitle() {
80 | return title;
81 | }
82 |
83 | public void setTitle(String title) {
84 | this.title = title;
85 | }
86 |
87 | public String getYearOfPublication() {
88 | return yearOfPublication;
89 | }
90 |
91 | public void setYearOfPublication(String yearOfPublication) {
92 | this.yearOfPublication = yearOfPublication;
93 | }
94 |
95 | public Set getAuthors() {
96 | return authors;
97 | }
98 |
99 | public void setAuthors(Set authors) {
100 | this.authors = authors;
101 | }
102 |
103 | public Set getTopics() {
104 | return topics;
105 | }
106 |
107 | public void setTopics(Set topics) {
108 | this.topics = topics;
109 | }
110 |
111 | public BigDecimal getPrice() {
112 | return price;
113 | }
114 |
115 | public void setPrice(BigDecimal price) {
116 | this.price = price;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/model/Topic.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.model;
2 |
3 |
4 | import com.fasterxml.jackson.annotation.JsonBackReference;
5 |
6 | import javax.persistence.*;
7 | import javax.validation.constraints.NotNull;
8 | import java.util.HashSet;
9 | import java.util.Set;
10 |
11 | @Entity
12 | public class Topic {
13 | @Id
14 | @GeneratedValue
15 | private Long id;
16 |
17 | @NotNull
18 | private String name;
19 |
20 | @NotNull
21 | @Lob
22 | private String description;
23 |
24 | @JsonBackReference
25 | @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "topics")
26 | private Set books = new HashSet<>();
27 |
28 | //Constructors
29 | public Topic(Long id, String name, String description, Set books) {
30 | this.id = id;
31 | this.name = name;
32 | this.description = description;
33 | this.books = books;
34 | }
35 |
36 | public Topic() {
37 | }
38 |
39 | public Topic(String name, String description) {
40 | this.name = name;
41 | this.description = description;
42 | }
43 |
44 | //getters and setters
45 | public Long getId() {
46 | return id;
47 | }
48 |
49 | public void setId(Long id) {
50 | this.id = id;
51 | }
52 |
53 | public String getName() {
54 | return name;
55 | }
56 |
57 | public void setName(String name) {
58 | this.name = name;
59 | }
60 |
61 | public String getDescription() {
62 | return description;
63 | }
64 |
65 | public void setDescription(String description) {
66 | this.description = description;
67 | }
68 |
69 | public Set getBooks() {
70 | return books;
71 | }
72 |
73 | public void setBooks(Set books) {
74 | this.books = books;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/repository/AuthorRepository.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.repository;
2 |
3 | import com.kvark900.api.model.Author;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | /**
8 | * Created by Keno&Kemo on 17.12.2017..
9 | */
10 | @Repository
11 | public interface AuthorRepository extends JpaRepository {
12 |
13 | Author findByNameAndSurnameAllIgnoreCase(String name, String surname);
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/repository/BookRepository.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.repository;
2 |
3 |
4 | import com.kvark900.api.model.Book;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.jpa.repository.Query;
7 | import org.springframework.stereotype.Repository;
8 |
9 | import java.util.List;
10 |
11 |
12 | @Repository
13 | public interface BookRepository extends JpaRepository {
14 | Book findByTitleAllIgnoreCase(String title);
15 |
16 | List findByTopicsId(Long id);
17 |
18 | List findByAuthorsId(Long id);
19 |
20 | @Query("SELECT b " +
21 | "FROM Book b " +
22 | "JOIN FETCH b.authors " +
23 | "jOIN FETCH b.topics")
24 | List findAll();
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/repository/TopicRepository.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.repository;
2 |
3 | import com.kvark900.api.model.Topic;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 |
8 | @Repository
9 | public interface TopicRepository extends JpaRepository {
10 | Topic findByNameAllIgnoreCase(String name);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/service/AuthorService.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.service;
2 |
3 | import com.kvark900.api.model.Author;
4 | import com.kvark900.api.repository.AuthorRepository;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.transaction.annotation.Transactional;
8 |
9 | import java.util.List;
10 | import java.util.Optional;
11 |
12 | /**
13 | * Created by Keno&Kemo on 17.12.2017..
14 | */
15 | @Service
16 | @Transactional
17 | public class AuthorService {
18 |
19 | private AuthorRepository authorRepository;
20 |
21 | public AuthorService() {
22 | }
23 |
24 | @Autowired
25 | public AuthorService(AuthorRepository authorRepository) {
26 | this.authorRepository = authorRepository;
27 | }
28 |
29 |
30 | public List findAll (){
31 | return authorRepository.findAll();
32 | }
33 |
34 | public Optional findById(Long id){
35 | return authorRepository.findById(id);
36 | }
37 |
38 | public Author findByNameAndSurname(String name, String surname){
39 | return authorRepository.findByNameAndSurnameAllIgnoreCase(name, surname);
40 | }
41 |
42 | public void save(Author author){
43 | authorRepository.save(author);
44 | }
45 |
46 | public void update(Author author){
47 | authorRepository.save(author);
48 | }
49 |
50 | public void delete(Long id){
51 | authorRepository.deleteById(id);
52 | }
53 |
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/service/BookService.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.service;
2 |
3 | import com.kvark900.api.model.Book;
4 | import com.kvark900.api.repository.BookRepository;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.transaction.annotation.Transactional;
8 |
9 | import java.util.List;
10 | import java.util.Optional;
11 |
12 | @Service
13 | @Transactional
14 | public class BookService {
15 | private BookRepository bookRepository;
16 |
17 | public BookService() {
18 | }
19 |
20 | @Autowired
21 | public BookService(BookRepository bookRepository) {
22 | this.bookRepository = bookRepository;
23 | }
24 |
25 | public List findAll() {
26 | return bookRepository.findAll();
27 | }
28 |
29 | public Optional findById(Long id) {
30 | return bookRepository.findById(id);
31 | }
32 |
33 | public Book findByTitle(String title) {
34 | return bookRepository.findByTitleAllIgnoreCase(title);
35 | }
36 |
37 | public List findByTopicsId(Long id) {
38 | return bookRepository.findByTopicsId(id);
39 | }
40 |
41 | public List findByAuthorsId(Long id) {
42 | return bookRepository.findByAuthorsId(id);
43 | }
44 |
45 |
46 | public void save(Book book) {
47 | bookRepository.save(book);
48 | }
49 |
50 | public void delete(Long id) {
51 | bookRepository.deleteById(id);
52 | }
53 |
54 | public void update(Book book) {
55 | bookRepository.save(book);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/kvark900/api/service/TopicService.java:
--------------------------------------------------------------------------------
1 | package com.kvark900.api.service;
2 |
3 | import com.kvark900.api.model.Topic;
4 | import com.kvark900.api.repository.TopicRepository;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.transaction.annotation.Transactional;
8 |
9 | import java.util.List;
10 | import java.util.Optional;
11 |
12 | @Service
13 | @Transactional
14 | public class TopicService {
15 | private final TopicRepository topicRepository;
16 |
17 |
18 | @Autowired
19 | public TopicService(TopicRepository topicRepository) {
20 | this.topicRepository = topicRepository;
21 | }
22 |
23 | public List findAll(){
24 | return topicRepository.findAll();
25 | }
26 |
27 | public Optional findById(Long id){
28 | return topicRepository.findById(id);
29 | }
30 |
31 | public Topic findByName (String name){return topicRepository.findByNameAllIgnoreCase(name);}
32 |
33 | public void save (Topic topic){
34 | topicRepository.save(topic);
35 | }
36 |
37 | public void delete(Long id){
38 | topicRepository.deleteById(id);
39 | }
40 |
41 | public void update (Topic topic){
42 | topicRepository.save(topic);
43 | }
44 | }
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # ===============================
2 | # = JPA / HIBERNATE
3 | # ===============================
4 | spring.jpa.show-sql = true
5 | spring.jpa.hibernate.ddl-auto=create
6 | spring.jpa.properties.hibernate.generate_statistics=true
7 | logging.level.org.hibernate.stat=debug
8 | spring.jpa.properties.hibernate.format_sql=true
9 | logging.level.org.hibernate.type=trace
10 |
11 | # ===============================
12 | # = DATA SOURCE
13 | # ===============================
14 | spring.datasource.url = jdbc:mysql://localhost:3307/springboot?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
15 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
16 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver
17 | spring.datasource.username = root
18 | spring.datasource.password = Kvark
19 | #spring.jpa.open-in-view=false
20 |
21 | logging.level.org.springframework.web=debug
22 | #logging.level.org.hibernate=debug
23 |
24 | jwt.header=Authorization
25 | jwt.secret=mySecret
26 | jwt.expiration=604800
27 | jwt.route.authentication.path=/auth
28 | jwt.route.authentication.refresh=/refresh
--------------------------------------------------------------------------------
/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | _ __ _ ___ ___ ___
2 | | |/ / | | / _ \ / _ \ / _ \
3 | | ' / __ __ __ _ _ __ | | __ | (_) | | | | | | | | |
4 | | < \ \ / / / _` | | '__| | |/ / \__, | | | | | | | | |
5 | | . \ \ V / | (_| | | | | < / / | |_| | | |_| |
6 | |_|\_\ \_/ \__,_| |_| |_|\_\ /_/ \___/ \___/
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/test/java/com/kvark900/BootifulApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.kvark900;
2 |
3 | import org.junit.Test;
4 | import org.junit.runner.RunWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit4.SpringRunner;
7 |
8 | @RunWith(SpringRunner.class)
9 | @SpringBootTest
10 | public class BootifulApplicationTests {
11 |
12 | @Test
13 | public void contextLoads() {
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/test/java/com/kvark900/TestAuthorController.java:
--------------------------------------------------------------------------------
1 | package com.kvark900;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.kvark900.api.controller.AuthorController;
5 | import com.kvark900.api.model.Author;
6 | import com.kvark900.api.service.AuthorService;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
12 | import org.springframework.boot.test.json.JacksonTester;
13 | import org.springframework.boot.test.mock.mockito.MockBean;
14 | import org.springframework.http.HttpStatus;
15 | import org.springframework.http.MediaType;
16 | import org.springframework.mock.web.MockHttpServletResponse;
17 | import org.springframework.test.context.junit4.SpringRunner;
18 | import org.springframework.test.web.servlet.MockMvc;
19 |
20 | import java.util.ArrayList;
21 | import java.util.Collections;
22 | import java.util.List;
23 |
24 | import static org.assertj.core.api.Java6Assertions.assertThat;
25 | import static org.mockito.BDDMockito.given;
26 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
27 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
29 |
30 | /**
31 | * Created by Keno&Kemo on 03.03.2018..
32 | */
33 |
34 | @RunWith(SpringRunner.class)
35 | @WebMvcTest(AuthorController.class)
36 | public class TestAuthorController {
37 |
38 | @Autowired
39 | private MockMvc mockMvc;
40 |
41 | @MockBean
42 | private AuthorService authorService;
43 |
44 | private JacksonTester authorJacksonTester;
45 |
46 |
47 | @Before
48 | public void setup() {
49 | JacksonTester.initFields(this, new ObjectMapper());
50 | }
51 |
52 | @Test
53 | public void testGetAllAuthorsWhenExist() throws Exception {
54 | Author author = new Author();
55 | author.setName("Richard");
56 | author.setSurname("Feynman");
57 |
58 | List allAuthors = Collections.singletonList(author);
59 |
60 | given(authorService.findAll()).willReturn(allAuthors);
61 |
62 | mockMvc.perform(get("/authors")
63 | .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
64 | .andExpect(status().isOk());
65 | }
66 |
67 | @Test
68 | public void testGetAllAuthorsWhenNotFound() throws Exception {
69 | List allAuthors = null;
70 |
71 | given(authorService.findAll()).willReturn(allAuthors);
72 |
73 | mockMvc.perform(get("/authors")
74 | .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
75 | .andExpect(status().isNotFound());
76 | }
77 |
78 | @Test
79 | public void testGetAllAuthorsWhenEmpty() throws Exception {
80 | List allAuthors = new ArrayList<>();
81 |
82 | given(authorService.findAll()).willReturn(allAuthors);
83 |
84 | mockMvc.perform(get("/authors")
85 | .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
86 | .andExpect(status().isNoContent());
87 | }
88 |
89 |
90 | @Test
91 | public void testSaveAuthor() throws Exception {
92 | // when
93 | MockHttpServletResponse response = mockMvc.perform(
94 | post("/authors").contentType(MediaType.APPLICATION_JSON_UTF8_VALUE).content(
95 | authorJacksonTester.write(new Author("Richard", "Feynman")).getJson()
96 | )).andReturn().getResponse();
97 |
98 | // then
99 | assertThat(response.getStatus()).isEqualTo(HttpStatus.CREATED.value());
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------