From 04ca52abe1b0ce206d69fa6a1c223fa23fddd421 Mon Sep 17 00:00:00 2001 From: Ren <75202059+LordRenDS@users.noreply.github.com> Date: Tue, 21 Mar 2023 00:18:10 +0200 Subject: [PATCH 01/28] Db repo (#31) * Configure project * Create prepopulate sql scripts * Create DTO for talent * Create talent entity and related tables * Create base SecurityConfig and TalentRepository * Edit config and add dependencies * Edit data and schema * Edit entity and dto: remove @NotNull from different linked tables, remove some fields from ShortTalent * Add to schema table: user_info, authority and user_authority * Create entity: UserInfo, Authority and UserAuthority * Create UserInfoRepository --- .gitignore | 33 ++ .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 59925 bytes .mvn/wrapper/maven-wrapper.properties | 18 + mvnw | 316 ++++++++++++++++++ mvnw.cmd | 188 +++++++++++ pom.xml | 91 +++++ .../com/provedcode/ProvedCodeApplication.java | 13 + .../com/provedcode/config/SecurityConfig.java | 28 ++ .../provedcode/talent/TalentController.java | 35 ++ .../talent/model/entity/Talent.java | 49 +++ .../model/entity/TalentAttachedFile.java | 28 ++ .../talent/model/entity/TalentContact.java | 26 ++ .../model/entity/TalentDescription.java | 30 ++ .../talent/model/entity/TalentLink.java | 28 ++ .../talent/model/entity/TalentSkill.java | 26 ++ .../talent/model/response/FullTalent.java | 16 + .../talent/model/response/ShortTalent.java | 15 + .../talent/repo/TalentRepository.java | 7 + .../user/model/entity/Authority.java | 25 ++ .../user/model/entity/UserAuthority.java | 25 ++ .../user/model/entity/UserInfo.java | 30 ++ .../user/repo/UserInfoRepository.java | 7 + src/main/resources/application.properties | 14 + src/main/resources/data.sql | 115 +++++++ src/main/resources/schema.sql | 82 +++++ .../demo/ProvedCodeApplicationTests.java | 13 + 26 files changed, 1258 insertions(+) create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 src/main/java/com/provedcode/ProvedCodeApplication.java create mode 100644 src/main/java/com/provedcode/config/SecurityConfig.java create mode 100644 src/main/java/com/provedcode/talent/TalentController.java create mode 100644 src/main/java/com/provedcode/talent/model/entity/Talent.java create mode 100644 src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java create mode 100644 src/main/java/com/provedcode/talent/model/entity/TalentContact.java create mode 100644 src/main/java/com/provedcode/talent/model/entity/TalentDescription.java create mode 100644 src/main/java/com/provedcode/talent/model/entity/TalentLink.java create mode 100644 src/main/java/com/provedcode/talent/model/entity/TalentSkill.java create mode 100644 src/main/java/com/provedcode/talent/model/response/FullTalent.java create mode 100644 src/main/java/com/provedcode/talent/model/response/ShortTalent.java create mode 100644 src/main/java/com/provedcode/talent/repo/TalentRepository.java create mode 100644 src/main/java/com/provedcode/user/model/entity/Authority.java create mode 100644 src/main/java/com/provedcode/user/model/entity/UserAuthority.java create mode 100644 src/main/java/com/provedcode/user/model/entity/UserInfo.java create mode 100644 src/main/java/com/provedcode/user/repo/UserInfoRepository.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/schema.sql create mode 100644 src/test/java/com/provedcode/demo/ProvedCodeApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..bf82ff01c6cdae4a1bb754a6e062954d77ac5c11 GIT binary patch literal 59925 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

dy!05UMhSKsK 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 VCNKd3JGtOvEm(5t0lFyE{{i--k}m)N literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..ca5ab4b --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..8a8fb22 --- /dev/null +++ b/mvnw @@ -0,0 +1,316 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`\\unset -f command; \\command -v java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..1d8ab01 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,188 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9d74fdc --- /dev/null +++ b/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.0.4 + + + com.provedcode + demo + 0.0.1-SNAPSHOT + ProvedCode + Demo project for Spring Boot + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/provedcode/ProvedCodeApplication.java b/src/main/java/com/provedcode/ProvedCodeApplication.java new file mode 100644 index 0000000..9267230 --- /dev/null +++ b/src/main/java/com/provedcode/ProvedCodeApplication.java @@ -0,0 +1,13 @@ +package com.provedcode; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ProvedCodeApplication { + + public static void main(String[] args) { + SpringApplication.run(ProvedCodeApplication.class, args); + } + +} diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java new file mode 100644 index 0000000..3d2d942 --- /dev/null +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -0,0 +1,28 @@ +package com.provedcode.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; +import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfig { + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(c -> c + .requestMatchers(antMatcher("/h2/**")).permitAll() + .requestMatchers(antMatcher("/api/**")).permitAll() + .anyRequest().denyAll() + ); + http.csrf().disable().headers().frameOptions().disable(); + http.sessionManagement().sessionCreationPolicy(STATELESS); + return http.build(); + } +} diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java new file mode 100644 index 0000000..4ef750d --- /dev/null +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -0,0 +1,35 @@ +package com.provedcode.talent; + +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.model.entity.TalentSkill; +import com.provedcode.talent.model.response.ShortTalent; +import com.provedcode.talent.repo.TalentRepository; +import lombok.AllArgsConstructor; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.stream.Collectors; + +@RestController +@AllArgsConstructor +public class TalentController { + TalentRepository talentRepository; + + @GetMapping("/api/talent/{id}") + ShortTalent getTalent(@PathVariable("id") long id) { + Talent talent = talentRepository.findById(id) + .orElseThrow( + () -> new UsernameNotFoundException( + "id " + id + " not found")); + return new ShortTalent( + talent.getId(), + talent.getImage(), + talent.getFirstName(), + talent.getLastName(), + talent.getSpecialization(), + talent.getTalentSkills().stream().map(TalentSkill::getSkill).collect(Collectors.toList()) + ); + } +} diff --git a/src/main/java/com/provedcode/talent/model/entity/Talent.java b/src/main/java/com/provedcode/talent/model/entity/Talent.java new file mode 100644 index 0000000..232de47 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/entity/Talent.java @@ -0,0 +1,49 @@ +package com.provedcode.talent.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import org.hibernate.validator.constraints.URL; + +import java.util.ArrayList; +import java.util.List; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "talent") +public class Talent { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, insertable = false, updatable = false) + private Long id; + @NotEmpty + @NotNull + @Column(name = "first_name", length = 20) + private String firstName; + @NotEmpty + @NotNull + @Column(name = "last_name", length = 20) + private String lastName; + @NotEmpty + @NotNull + @Column(name = "specialization", length = 30) + private String specialization; + @URL + @Column(name = "image", length = 100) + private String image; + @OneToOne(mappedBy = "talent", orphanRemoval = true) + private TalentDescription talentDescription; + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + private List talentLinks = new ArrayList<>(); + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + private List talentSkills = new ArrayList<>(); + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + private List talentContacts = new ArrayList<>(); + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + private List talentAttachedFiles = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java b/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java new file mode 100644 index 0000000..d0eada0 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java @@ -0,0 +1,28 @@ +package com.provedcode.talent.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.validator.constraints.URL; + +@Getter +@Setter +@Entity +@Table(name = "talent_attached_file") +public class TalentAttachedFile { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @NotNull + @Column(name = "talent_id", nullable = false) + private Long talentId; + @URL + @Column(name = "attached_file", length = 100) + private String attachedFile; + @NotNull + @ManyToOne + @JoinColumn(name = "talent_id", insertable = false, updatable = false) + private Talent talent; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentContact.java b/src/main/java/com/provedcode/talent/model/entity/TalentContact.java new file mode 100644 index 0000000..78bc1e8 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/entity/TalentContact.java @@ -0,0 +1,26 @@ +package com.provedcode.talent.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "talent_contact") +public class TalentContact { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @NotNull + @Column(name = "talent_id", nullable = false) + private Long talentId; + @Column(name = "contact") + private String contact; + @NotNull + @ManyToOne + @JoinColumn(name = "talent_id", insertable = false, updatable = false) + private Talent talent; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java b/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java new file mode 100644 index 0000000..ff48a7c --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java @@ -0,0 +1,30 @@ +package com.provedcode.talent.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "talent_description") +public class TalentDescription { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long id; + @NotNull + @Column(name = "talent_id", nullable = false) + private Long talentId; + @Column(name = "BIO") + private String bio; + @Column(name = "addition_info") + private String additionalInfo; + @NotNull + @OneToOne(orphanRemoval = true) + @JoinColumn(name = "talent_id", insertable = false, updatable = false) + private Talent talent; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentLink.java b/src/main/java/com/provedcode/talent/model/entity/TalentLink.java new file mode 100644 index 0000000..8ff7d7c --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/entity/TalentLink.java @@ -0,0 +1,28 @@ +package com.provedcode.talent.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.validator.constraints.URL; + +@Getter +@Setter +@Entity +@Table(name = "talent_link") +public class TalentLink { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + private Long id; + @NotNull + @Column(name = "talent_id", nullable = false) + private Long talentId; + @URL + @Column(name = "link") + private String link; + @NotNull + @ManyToOne + @JoinColumn(name = "talent_id", insertable = false, updatable = false) + private Talent talent; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java b/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java new file mode 100644 index 0000000..4f312b1 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java @@ -0,0 +1,26 @@ +package com.provedcode.talent.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Entity +@Table(name = "talent_skill") +public class TalentSkill { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @NotNull + @Column(name = "talent_id", nullable = false) + private Long talentId; + @Column(name = "skill") + private String skill; + @NotNull + @ManyToOne + @JoinColumn(name = "talent_id", insertable = false, updatable = false) + private Talent talent; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/response/FullTalent.java b/src/main/java/com/provedcode/talent/model/response/FullTalent.java new file mode 100644 index 0000000..b53716f --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/response/FullTalent.java @@ -0,0 +1,16 @@ +package com.provedcode.talent.model.response; + +import java.util.List; + +public record FullTalent( + String image, + String firstName, + String lastName, + String specialization, + List fullSkills, + String bio, + List contacts, + List links, + List attachedFiles +) { +} diff --git a/src/main/java/com/provedcode/talent/model/response/ShortTalent.java b/src/main/java/com/provedcode/talent/model/response/ShortTalent.java new file mode 100644 index 0000000..532ff26 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/response/ShortTalent.java @@ -0,0 +1,15 @@ +package com.provedcode.talent.model.response; + +import com.provedcode.talent.model.entity.TalentSkill; + +import java.util.List; + +public record ShortTalent( + long id, + String image, + String firstName, + String lastName, + String specialization, + List shortDescription +) { +} diff --git a/src/main/java/com/provedcode/talent/repo/TalentRepository.java b/src/main/java/com/provedcode/talent/repo/TalentRepository.java new file mode 100644 index 0000000..b2bac22 --- /dev/null +++ b/src/main/java/com/provedcode/talent/repo/TalentRepository.java @@ -0,0 +1,7 @@ +package com.provedcode.talent.repo; + +import com.provedcode.talent.model.entity.Talent; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TalentRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/model/entity/Authority.java b/src/main/java/com/provedcode/user/model/entity/Authority.java new file mode 100644 index 0000000..384c35a --- /dev/null +++ b/src/main/java/com/provedcode/user/model/entity/Authority.java @@ -0,0 +1,25 @@ +package com.provedcode.user.model.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.util.LinkedHashSet; +import java.util.Set; + +@Getter +@Setter +@Entity +@Table(name = "authority") +public class Authority { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @NotEmpty + @NotNull + @Column(name = "authority", length = 50) + private String authority; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java new file mode 100644 index 0000000..5a71b00 --- /dev/null +++ b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java @@ -0,0 +1,25 @@ +package com.provedcode.user.model.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.util.LinkedHashSet; +import java.util.Set; + +@Getter +@Setter +@Entity +@Table(name = "user_authority") +public class UserAuthority { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + @ManyToOne + @JoinColumn(name = "user_id") + private UserInfo userInfo; + @ManyToOne + @JoinColumn(name = "authority_id") + private Authority authority; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/model/entity/UserInfo.java b/src/main/java/com/provedcode/user/model/entity/UserInfo.java new file mode 100644 index 0000000..27f9a8e --- /dev/null +++ b/src/main/java/com/provedcode/user/model/entity/UserInfo.java @@ -0,0 +1,30 @@ +package com.provedcode.user.model.entity; + +import com.provedcode.talent.model.entity.Talent; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +import java.util.LinkedHashSet; +import java.util.Set; + +@Getter +@Setter +@Entity +@Table(name = "user_info") +public class UserInfo { + @NotNull + @Id + @Column(name = "id", nullable = false) + private Long id; + @Column(name = "login", length = 100) + private String login; + @Column(name = "password") + private String password; + @OneToOne(orphanRemoval = true) + @JoinColumn(name = "id") + private Talent talent; + @OneToMany(mappedBy = "userInfo", orphanRemoval = true) + private Set userAuthorities = new LinkedHashSet<>(); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java new file mode 100644 index 0000000..bb340ee --- /dev/null +++ b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java @@ -0,0 +1,7 @@ +package com.provedcode.user.repo; + +import com.provedcode.user.model.entity.UserInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserInfoRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..9541894 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,14 @@ +server.port=28852 +management.endpoints.web.exposure.include=* +management.endpoint.shutdown.enabled=true +## +spring.jackson.property-naming-strategy=SNAKE_CASE +## +logging.level.cinema.controller=info +## +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.url=jdbc:h2:mem:/sampleDB +spring.h2.console.enabled=true +spring.h2.console.path=/h2 +## +spring.jpa.hibernate.ddl-auto=none diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 0000000..5604d04 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,115 @@ +insert into talent (first_name, last_name, specialization, image) +values ('Serhii', 'Soloviov', 'Java-Developer', 'http://image'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Default bio', 'Default addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'first_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'second_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'third_skill'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Mykhailo', 'Ordyntsev', 'Java-Developer', 'http://MykhailoOrdyntsevImage'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Mykhailo Ordyntsev bio', 'Mykhailo Ordyntsev addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://MykhailoOrdyntsev_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://MykhailoOrdyntsev_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://MykhailoOrdyntsev_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_first_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_second_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_skill'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Denis', 'Boyko', 'Java-Developer', 'http://DenisBoykoImage'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Denis Boyko bio', 'Denis Boyko addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DenisBoyko_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DenisBoyko_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DenisBoyko_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_first_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_skill'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Ihor', 'Schurenko', 'Java-Developer', 'http://IhorShchurenkoImage'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Ihor Shchurenko bio', 'Ihor Shchurenko addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://IhorShchurenko_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://IhorShchurenko_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://IhorShchurenko_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_first_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_skill'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_skill'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_file'); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..06f6c86 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,82 @@ +--talent tables-- +CREATE TABLE talent ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + first_name VARCHAR(20) NOT NULL, + last_name VARCHAR(20) NOT NULL, + specialization VARCHAR(30) NOT NULL, + image VARCHAR(100), + CONSTRAINT pk_talent PRIMARY KEY (id) +); + +create TABLE talent_description ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + talent_id BIGINT NOT NULL, + BIO VARCHAR(255) NOT NULL, + addition_info VARCHAR(255) NOT NULL, + CONSTRAINT pk_talent_description PRIMARY KEY (id) +); + +alter table talent_description add CONSTRAINT FK_TALENT_DESCRIPTION_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id); + +create TABLE talent_link ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + talent_id BIGINT NOT NULL, + link VARCHAR(255) NOT NULL, + CONSTRAINT pk_talent_link PRIMARY KEY (id) +); + +alter table talent_link add CONSTRAINT FK_TALENT_LINK_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id); + +create TABLE talent_skill ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + talent_id BIGINT NOT NULL, + skill VARCHAR(255) NOT NULL, + CONSTRAINT pk_talent_skill PRIMARY KEY (id) +); + +alter table talent_skill add CONSTRAINT FK_TALENT_SKILL_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id); + +create TABLE talent_contact ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + talent_id BIGINT NOT NULL, + contact VARCHAR(255) NOT NULL, + CONSTRAINT pk_talent_contact PRIMARY KEY (id) +); + +alter table talent_contact add CONSTRAINT FK_TALENT_CONTACT_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id); + +create TABLE talent_attached_file ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + talent_id BIGINT NOT NULL, + attached_file VARCHAR(100) NOT NULL, + CONSTRAINT pk_talent_attached_file PRIMARY KEY (id) +); + +alter table talent_attached_file add CONSTRAINT FK_TALENT_ATTACHED_FILE_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id); + +--user tables-- +CREATE TABLE user_info ( + id BIGINT NOT NULL, + login VARCHAR(100), + password VARCHAR(255), + CONSTRAINT pk_user_info PRIMARY KEY (id) +); + +ALTER TABLE user_info ADD CONSTRAINT FK_USER_INFO_ON_ID FOREIGN KEY (id) REFERENCES talent (id); + +CREATE TABLE authority ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + authority VARCHAR(50) NOT NULL, + CONSTRAINT pk_authority PRIMARY KEY (id) +); + +CREATE TABLE user_authority ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, + user_id BIGINT, + authority_id BIGINT, + CONSTRAINT pk_user_authority PRIMARY KEY (id) +); + +ALTER TABLE user_authority ADD CONSTRAINT FK_USER_AUTHORITY_ON_AUTHORITY FOREIGN KEY (authority_id) REFERENCES authority (id); + +ALTER TABLE user_authority ADD CONSTRAINT FK_USER_AUTHORITY_ON_USER FOREIGN KEY (user_id) REFERENCES user_info (id); \ No newline at end of file diff --git a/src/test/java/com/provedcode/demo/ProvedCodeApplicationTests.java b/src/test/java/com/provedcode/demo/ProvedCodeApplicationTests.java new file mode 100644 index 0000000..ebfbdd7 --- /dev/null +++ b/src/test/java/com/provedcode/demo/ProvedCodeApplicationTests.java @@ -0,0 +1,13 @@ +package com.provedcode.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class ProvedCodeApplicationTests { + + @Test + void contextLoads() { + } + +} From 1d52efb996c5cee5f24b73a8863e36bd0da9c16a Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 21 Mar 2023 01:07:50 +0100 Subject: [PATCH 02/28] Updated TalentController Created TalentEntityRepository Updated TalentRepository Created PageConfig, that takes config from application.properties Created ShortTalentDTO Created TalentMapper and his impl Created TalentService and his impl ------------ Added `GET(/api/talents)` endpoint, that returns us the `List` Added pagination to `GET(/api/talents)` endpoint - To use pagination you may use RequestParams: - page (gives us requested page) - size (gives us requested size of page) --- .../com/provedcode/ProvedCodeApplication.java | 2 ++ .../com/provedcode/config/PageConfig.java | 9 ++++++ .../com/provedcode/service/TalentService.java | 12 +++++++ .../service/impl/TalentServiceImpl.java | 32 +++++++++++++++++++ .../provedcode/talent/TalentController.java | 20 +++++++++--- .../talent/mapper/TalentMapper.java | 18 +++++++++++ .../talent/mapper/impl/TalentMapperImpl.java | 8 +++++ .../talent/model/dto/ShortTalentDTO.java | 16 ++++++++++ .../talent/repo/TalentRepository.java | 8 ++++- .../repo/db/TalentEntityRepository.java | 24 ++++++++++++++ src/main/resources/application.properties | 3 ++ 11 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/provedcode/config/PageConfig.java create mode 100644 src/main/java/com/provedcode/service/TalentService.java create mode 100644 src/main/java/com/provedcode/service/impl/TalentServiceImpl.java create mode 100644 src/main/java/com/provedcode/talent/mapper/TalentMapper.java create mode 100644 src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java create mode 100644 src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java create mode 100644 src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java diff --git a/src/main/java/com/provedcode/ProvedCodeApplication.java b/src/main/java/com/provedcode/ProvedCodeApplication.java index 9267230..fc86627 100644 --- a/src/main/java/com/provedcode/ProvedCodeApplication.java +++ b/src/main/java/com/provedcode/ProvedCodeApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @SpringBootApplication +@ConfigurationPropertiesScan public class ProvedCodeApplication { public static void main(String[] args) { diff --git a/src/main/java/com/provedcode/config/PageConfig.java b/src/main/java/com/provedcode/config/PageConfig.java new file mode 100644 index 0000000..a93f385 --- /dev/null +++ b/src/main/java/com/provedcode/config/PageConfig.java @@ -0,0 +1,9 @@ +package com.provedcode.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "page-config") +public record PageConfig( + int defaultPageNum, + int defaultPageSize +) {} diff --git a/src/main/java/com/provedcode/service/TalentService.java b/src/main/java/com/provedcode/service/TalentService.java new file mode 100644 index 0000000..1519b59 --- /dev/null +++ b/src/main/java/com/provedcode/service/TalentService.java @@ -0,0 +1,12 @@ +package com.provedcode.service; + +import com.provedcode.talent.model.dto.ShortTalentDTO; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +public interface TalentService { + List getTalentsPage(Optional page, Optional size); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java new file mode 100644 index 0000000..88b1960 --- /dev/null +++ b/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java @@ -0,0 +1,32 @@ +package com.provedcode.service.impl; + +import com.provedcode.config.PageConfig; +import com.provedcode.service.TalentService; +import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.repo.TalentRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@Slf4j +@AllArgsConstructor +public class TalentServiceImpl implements TalentService { + TalentMapper talentMapper; + TalentRepository talentRepository; + PageConfig pageConfig; + + @Override + public List getTalentsPage(Optional page, Optional size) { + log.info("page = {}", pageConfig); + return talentRepository.getTalentsPage( + PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) + .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) + .toList(); + } +} diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index 4ef750d..fb6c1ab 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -1,21 +1,26 @@ package com.provedcode.talent; +import com.provedcode.service.TalentService; +import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.model.entity.TalentSkill; import com.provedcode.talent.model.response.ShortTalent; import com.provedcode.talent.repo.TalentRepository; +import com.provedcode.talent.repo.db.TalentEntityRepository; import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @RestController @AllArgsConstructor public class TalentController { - TalentRepository talentRepository; + TalentEntityRepository talentRepository; + TalentService talentService; @GetMapping("/api/talent/{id}") ShortTalent getTalent(@PathVariable("id") long id) { @@ -32,4 +37,11 @@ ShortTalent getTalent(@PathVariable("id") long id) { talent.getTalentSkills().stream().map(TalentSkill::getSkill).collect(Collectors.toList()) ); } + + @GetMapping("/api/talents") + @ResponseStatus(HttpStatus.OK) + List getTalents(@RequestParam(value = "page") Optional page, + @RequestParam(value = "size") Optional size) { + return talentService.getTalentsPage(page, size); + } } diff --git a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java new file mode 100644 index 0000000..1597081 --- /dev/null +++ b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java @@ -0,0 +1,18 @@ +package com.provedcode.talent.mapper; + +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.model.entity.TalentSkill; + +public interface TalentMapper { + default ShortTalentDTO talentToShortTalentDTO(Talent talent) { + return ShortTalentDTO.builder() + .id(talent.getId()) + .image(talent.getImage()) + .firstname(talent.getFirstName()) + .lastname(talent.getLastName()) + .specialization(talent.getSpecialization()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .build(); + } +} diff --git a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java new file mode 100644 index 0000000..6dc6a03 --- /dev/null +++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java @@ -0,0 +1,8 @@ +package com.provedcode.talent.mapper.impl; + +import com.provedcode.talent.mapper.TalentMapper; +import org.springframework.stereotype.Component; + +@Component +public class TalentMapperImpl implements TalentMapper { +} diff --git a/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java new file mode 100644 index 0000000..a9ca9a7 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java @@ -0,0 +1,16 @@ +package com.provedcode.talent.model.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record ShortTalentDTO( + Long id, + String image, + String firstname, + String lastname, + String specialization, + List skills +) { +} diff --git a/src/main/java/com/provedcode/talent/repo/TalentRepository.java b/src/main/java/com/provedcode/talent/repo/TalentRepository.java index b2bac22..9dcd4fc 100644 --- a/src/main/java/com/provedcode/talent/repo/TalentRepository.java +++ b/src/main/java/com/provedcode/talent/repo/TalentRepository.java @@ -1,7 +1,13 @@ package com.provedcode.talent.repo; import com.provedcode.talent.model.entity.Talent; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; -public interface TalentRepository extends JpaRepository { +import java.util.List; + +public interface TalentRepository { + + List getTalentsPage(PageRequest page); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java new file mode 100644 index 0000000..5781984 --- /dev/null +++ b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java @@ -0,0 +1,24 @@ +package com.provedcode.talent.repo.db; + +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.TalentRepository; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +public interface TalentEntityRepository extends + JpaRepository, + TalentRepository { + @Transactional(readOnly = true) + default List getTalents() { + return findAll(); + } + + @Override + @Transactional(readOnly = true) + default List getTalentsPage(PageRequest page) { + return findAll(page).stream().toList(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9541894..14daa19 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,3 +12,6 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2 ## spring.jpa.hibernate.ddl-auto=none +## +page-config.default-page-num=0 +page-config.default-page-size=5 \ No newline at end of file From badfc53b2f63a0258691e630232f188807cb8304 Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 21 Mar 2023 11:59:52 +0100 Subject: [PATCH 03/28] Created TalentExceptionHandler Updated TalentController Updated TalentRepository Created TalentEntityRepository Created FullTalentDTO Created TalentServiceMock Created PageConfig, that takes config from application.properties Created ShortTalentDTO Created TalentMapper and his impl Created TalentService and his impl ------------ Added `GET(/api/talents)` endpoint, that returns us the `List` Added pagination to `GET(/api/talents)` endpoint - To use pagination you may use RequestParams: - page (gives us requested page) - size (gives us requested size of page) Added simple exception handling --- .../handlers/TalentExceptionHandler.java | 14 ++++++ .../com/provedcode/service/TalentService.java | 5 +- .../service/impl/TalentServiceImpl.java | 24 ++++++++- .../service/mock/TalentServiceMock.java | 50 +++++++++++++++++++ .../provedcode/talent/TalentController.java | 24 ++------- .../talent/mapper/TalentMapper.java | 1 + .../talent/model/dto/FullTalentDTO.java | 6 +++ .../talent/model/entity/Talent.java | 1 + .../talent/repo/TalentRepository.java | 3 ++ .../repo/db/TalentEntityRepository.java | 4 ++ 10 files changed, 108 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/provedcode/handlers/TalentExceptionHandler.java create mode 100644 src/main/java/com/provedcode/service/mock/TalentServiceMock.java create mode 100644 src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java diff --git a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java new file mode 100644 index 0000000..86300b8 --- /dev/null +++ b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java @@ -0,0 +1,14 @@ +package com.provedcode.handlers; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.server.ResponseStatusException; + +@ControllerAdvice +public class TalentExceptionHandler { + @ExceptionHandler(ResponseStatusException.class) + private ResponseEntity responseStatusExceptionHandler(ResponseStatusException exception) { + return ResponseEntity.status(exception.getStatusCode()).body(exception.getBody()); + } +} diff --git a/src/main/java/com/provedcode/service/TalentService.java b/src/main/java/com/provedcode/service/TalentService.java index 1519b59..271b4e3 100644 --- a/src/main/java/com/provedcode/service/TalentService.java +++ b/src/main/java/com/provedcode/service/TalentService.java @@ -1,12 +1,15 @@ package com.provedcode.service; import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.Talent; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; -@Service + public interface TalentService { List getTalentsPage(Optional page, Optional size); + + Talent getTalentById(long id); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java index 88b1960..a0727b5 100644 --- a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java @@ -2,17 +2,23 @@ import com.provedcode.config.PageConfig; import com.provedcode.service.TalentService; +import com.provedcode.talent.TalentController; import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.TalentRepository; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Optional; +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + @Service @Slf4j @AllArgsConstructor @@ -23,10 +29,24 @@ public class TalentServiceImpl implements TalentService { @Override public List getTalentsPage(Optional page, Optional size) { - log.info("page = {}", pageConfig); + if (page.orElse(pageConfig.defaultPageNum()) < 0) { + throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); + } + if (size.orElse(pageConfig.defaultPageSize()) <= 0) { + throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); + } return talentRepository.getTalentsPage( - PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) + PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) .toList(); } + + @Override + public Talent getTalentById(long id) { + Optional talent = talentRepository.findById(id); + if (talent.isEmpty()){ + throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); + } + return null; + } } diff --git a/src/main/java/com/provedcode/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/service/mock/TalentServiceMock.java new file mode 100644 index 0000000..dc080b3 --- /dev/null +++ b/src/main/java/com/provedcode/service/mock/TalentServiceMock.java @@ -0,0 +1,50 @@ +package com.provedcode.service.mock; + +import com.provedcode.config.PageConfig; +import com.provedcode.service.TalentService; +import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.TalentRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.web.server.ResponseStatusException; + +import java.util.List; +import java.util.Optional; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +//@Service +@Slf4j +@AllArgsConstructor +public class TalentServiceMock implements TalentService { + TalentMapper talentMapper; + TalentRepository talentRepository; + PageConfig pageConfig; + + @Override + public List getTalentsPage(Optional page, Optional size) { + if (page.orElse(pageConfig.defaultPageNum()) < 0) { + throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); + } + if (size.orElse(pageConfig.defaultPageSize()) <= 0) { + throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); + } + return talentRepository.getTalentsPage( + PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) + .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) + .toList(); + } + + @Override + public Talent getTalentById(long id) { + Optional talent = talentRepository.findById(id); + if (talent.isEmpty()){ + throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index fb6c1ab..8a608e1 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -3,39 +3,21 @@ import com.provedcode.service.TalentService; import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.model.entity.TalentSkill; -import com.provedcode.talent.model.response.ShortTalent; -import com.provedcode.talent.repo.TalentRepository; -import com.provedcode.talent.repo.db.TalentEntityRepository; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @RestController @AllArgsConstructor public class TalentController { - TalentEntityRepository talentRepository; TalentService talentService; - @GetMapping("/api/talent/{id}") - ShortTalent getTalent(@PathVariable("id") long id) { - Talent talent = talentRepository.findById(id) - .orElseThrow( - () -> new UsernameNotFoundException( - "id " + id + " not found")); - return new ShortTalent( - talent.getId(), - talent.getImage(), - talent.getFirstName(), - talent.getLastName(), - talent.getSpecialization(), - talent.getTalentSkills().stream().map(TalentSkill::getSkill).collect(Collectors.toList()) - ); + @GetMapping("/api/talents/{id}") + Talent getTalent(@PathVariable("id") long id) { + return talentService.getTalentById(id); } @GetMapping("/api/talents") diff --git a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java index 1597081..0a860a1 100644 --- a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java +++ b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java @@ -15,4 +15,5 @@ default ShortTalentDTO talentToShortTalentDTO(Talent talent) { .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) .build(); } + } diff --git a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java new file mode 100644 index 0000000..750c766 --- /dev/null +++ b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java @@ -0,0 +1,6 @@ +package com.provedcode.talent.model.dto; + +public record FullTalentDTO( + +) { +} diff --git a/src/main/java/com/provedcode/talent/model/entity/Talent.java b/src/main/java/com/provedcode/talent/model/entity/Talent.java index 232de47..f52f6f1 100644 --- a/src/main/java/com/provedcode/talent/model/entity/Talent.java +++ b/src/main/java/com/provedcode/talent/model/entity/Talent.java @@ -46,4 +46,5 @@ public class Talent { private List talentContacts = new ArrayList<>(); @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) private List talentAttachedFiles = new ArrayList<>(); + } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/TalentRepository.java b/src/main/java/com/provedcode/talent/repo/TalentRepository.java index 9dcd4fc..b2fe0de 100644 --- a/src/main/java/com/provedcode/talent/repo/TalentRepository.java +++ b/src/main/java/com/provedcode/talent/repo/TalentRepository.java @@ -6,8 +6,11 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; public interface TalentRepository { List getTalentsPage(PageRequest page); + + Optional findById(Long aLong); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java index 5781984..aaa9688 100644 --- a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java +++ b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java @@ -7,6 +7,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; public interface TalentEntityRepository extends JpaRepository, @@ -21,4 +22,7 @@ default List getTalents() { default List getTalentsPage(PageRequest page) { return findAll(page).stream().toList(); } + + @Override + Optional findById(Long aLong); } \ No newline at end of file From 2fb094232526c2ea4c1a368ca83188e0a6e3669a Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 21 Mar 2023 14:49:57 +0100 Subject: [PATCH 04/28] Updated TalentController: Added new endpoint `GET(/talents/{id})`, that returns to us the FullTalentDTO Updated FullTalentDTO Updated TalentMapper Updated TalentService --- .../com/provedcode/service/TalentService.java | 5 ++--- .../service/impl/TalentServiceImpl.java | 6 +++--- .../service/mock/TalentServiceMock.java | 5 +++-- .../provedcode/talent/TalentController.java | 3 ++- .../talent/mapper/TalentMapper.java | 20 +++++++++++++++++-- .../talent/model/dto/FullTalentDTO.java | 20 ++++++++++++++++--- 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/provedcode/service/TalentService.java b/src/main/java/com/provedcode/service/TalentService.java index 271b4e3..a7915ca 100644 --- a/src/main/java/com/provedcode/service/TalentService.java +++ b/src/main/java/com/provedcode/service/TalentService.java @@ -1,8 +1,7 @@ package com.provedcode.service; +import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; -import com.provedcode.talent.model.entity.Talent; -import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @@ -11,5 +10,5 @@ public interface TalentService { List getTalentsPage(Optional page, Optional size); - Talent getTalentById(long id); + FullTalentDTO getTalentById(long id); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java index a0727b5..0d7443f 100644 --- a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java @@ -2,8 +2,8 @@ import com.provedcode.config.PageConfig; import com.provedcode.service.TalentService; -import com.provedcode.talent.TalentController; import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.TalentRepository; @@ -42,11 +42,11 @@ public List getTalentsPage(Optional page, Optional talent = talentRepository.findById(id); if (talent.isEmpty()){ throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); } - return null; + return talentMapper.talentToFullTalentDTO(talent.get()); } } diff --git a/src/main/java/com/provedcode/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/service/mock/TalentServiceMock.java index dc080b3..39471e7 100644 --- a/src/main/java/com/provedcode/service/mock/TalentServiceMock.java +++ b/src/main/java/com/provedcode/service/mock/TalentServiceMock.java @@ -3,6 +3,7 @@ import com.provedcode.config.PageConfig; import com.provedcode.service.TalentService; import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.TalentRepository; @@ -40,11 +41,11 @@ public List getTalentsPage(Optional page, Optional talent = talentRepository.findById(id); if (talent.isEmpty()){ throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); } - return null; + return talentMapper.talentToFullTalentDTO(talent.get()); } } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index 8a608e1..382fe85 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -1,6 +1,7 @@ package com.provedcode.talent; import com.provedcode.service.TalentService; +import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.model.entity.Talent; import lombok.AllArgsConstructor; @@ -16,7 +17,7 @@ public class TalentController { TalentService talentService; @GetMapping("/api/talents/{id}") - Talent getTalent(@PathVariable("id") long id) { + FullTalentDTO getTalent(@PathVariable("id") long id) { return talentService.getTalentById(id); } diff --git a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java index 0a860a1..ca767a4 100644 --- a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java +++ b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java @@ -1,8 +1,8 @@ package com.provedcode.talent.mapper; +import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; -import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.model.entity.TalentSkill; +import com.provedcode.talent.model.entity.*; public interface TalentMapper { default ShortTalentDTO talentToShortTalentDTO(Talent talent) { @@ -16,4 +16,20 @@ default ShortTalentDTO talentToShortTalentDTO(Talent talent) { .build(); } + default FullTalentDTO talentToFullTalentDTO(Talent talent) { + return FullTalentDTO.builder() + .id(talent.getId()) + .firstname(talent.getFirstName()) + .lastname(talent.getLastName()) + .bio(talent.getTalentDescription().getBio()) + .additionalInfo(talent.getTalentDescription().getAdditionalInfo()) + .image(talent.getImage()) + .specialization(talent.getSpecialization()) + .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) + .contacts(talent.getTalentContacts().stream().map(TalentContact::getContact).toList()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .attachedFiles(talent.getTalentAttachedFiles().stream().map(TalentAttachedFile::getAttachedFile).toList()) + .build(); + } + } diff --git a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java index 750c766..a8a72c2 100644 --- a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java +++ b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java @@ -1,6 +1,20 @@ package com.provedcode.talent.model.dto; -public record FullTalentDTO( +import lombok.Builder; -) { -} +import java.util.List; + +@Builder +public record FullTalentDTO ( + Long id, + String firstname, + String lastname, + String image, + String specialization, + String additionalInfo, + String bio, + List skills, + List links, + List contacts, + List attachedFiles +) {} From 775886e9d2b1951f948796e10fed52325ccb9e46 Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 21 Mar 2023 15:21:35 +0100 Subject: [PATCH 05/28] Refactored project structure Created pagination.properties --- .../java/com/provedcode/config/PageConfig.java | 14 +++++++++++++- .../com/provedcode/talent/TalentController.java | 3 +-- .../{ => talent}/service/TalentService.java | 2 +- .../service/impl/TalentServiceImpl.java | 4 ++-- .../service/mock/TalentServiceMock.java | 4 ++-- src/main/resources/application.properties | 3 --- src/main/resources/pagination.properties | 3 +++ 7 files changed, 22 insertions(+), 11 deletions(-) rename src/main/java/com/provedcode/{ => talent}/service/TalentService.java (89%) rename src/main/java/com/provedcode/{ => talent}/service/impl/TalentServiceImpl.java (95%) rename src/main/java/com/provedcode/{ => talent}/service/mock/TalentServiceMock.java (95%) create mode 100644 src/main/resources/pagination.properties diff --git a/src/main/java/com/provedcode/config/PageConfig.java b/src/main/java/com/provedcode/config/PageConfig.java index a93f385..ed70939 100644 --- a/src/main/java/com/provedcode/config/PageConfig.java +++ b/src/main/java/com/provedcode/config/PageConfig.java @@ -1,9 +1,21 @@ package com.provedcode.config; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +@EnableConfigurationProperties +@PropertySource("pagination.properties") @ConfigurationProperties(prefix = "page-config") +@Slf4j public record PageConfig( int defaultPageNum, int defaultPageSize -) {} +) { + @PostConstruct + void print() { + log.info("page-props = {} ", this); + } +} diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index 382fe85..0ca8e61 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -1,9 +1,8 @@ package com.provedcode.talent; -import com.provedcode.service.TalentService; +import com.provedcode.talent.service.TalentService; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; -import com.provedcode.talent.model.entity.Talent; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/provedcode/service/TalentService.java b/src/main/java/com/provedcode/talent/service/TalentService.java similarity index 89% rename from src/main/java/com/provedcode/service/TalentService.java rename to src/main/java/com/provedcode/talent/service/TalentService.java index a7915ca..3b1147a 100644 --- a/src/main/java/com/provedcode/service/TalentService.java +++ b/src/main/java/com/provedcode/talent/service/TalentService.java @@ -1,4 +1,4 @@ -package com.provedcode.service; +package com.provedcode.talent.service; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; diff --git a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java similarity index 95% rename from src/main/java/com/provedcode/service/impl/TalentServiceImpl.java rename to src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java index 0d7443f..5f23955 100644 --- a/src/main/java/com/provedcode/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -1,7 +1,7 @@ -package com.provedcode.service.impl; +package com.provedcode.talent.service.impl; import com.provedcode.config.PageConfig; -import com.provedcode.service.TalentService; +import com.provedcode.talent.service.TalentService; import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; diff --git a/src/main/java/com/provedcode/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java similarity index 95% rename from src/main/java/com/provedcode/service/mock/TalentServiceMock.java rename to src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java index 39471e7..84ba719 100644 --- a/src/main/java/com/provedcode/service/mock/TalentServiceMock.java +++ b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java @@ -1,7 +1,7 @@ -package com.provedcode.service.mock; +package com.provedcode.talent.service.mock; import com.provedcode.config.PageConfig; -import com.provedcode.service.TalentService; +import com.provedcode.talent.service.TalentService; import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 14daa19..9541894 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,6 +12,3 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2 ## spring.jpa.hibernate.ddl-auto=none -## -page-config.default-page-num=0 -page-config.default-page-size=5 \ No newline at end of file diff --git a/src/main/resources/pagination.properties b/src/main/resources/pagination.properties new file mode 100644 index 0000000..7ba1262 --- /dev/null +++ b/src/main/resources/pagination.properties @@ -0,0 +1,3 @@ +## +page-config.default-page-num=0 +page-config.default-page-size=5 \ No newline at end of file From 9180e5f39953c9cd729d794597d9e3b6dcf9c6e8 Mon Sep 17 00:00:00 2001 From: Mykhailo Ordyntsev <102993813+Maslyna@users.noreply.github.com> Date: Wed, 22 Mar 2023 18:12:46 +0100 Subject: [PATCH 06/28] BUGFIX: `BeanDefinitionStoreException: Failed to parse configuration class [com.provedcode.config.PageConfig]` (#33) --- src/main/java/com/provedcode/config/PageConfig.java | 1 - src/main/resources/application.properties | 3 +++ src/main/resources/pagination.properties | 3 --- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/provedcode/config/PageConfig.java b/src/main/java/com/provedcode/config/PageConfig.java index ed70939..1922716 100644 --- a/src/main/java/com/provedcode/config/PageConfig.java +++ b/src/main/java/com/provedcode/config/PageConfig.java @@ -7,7 +7,6 @@ import org.springframework.context.annotation.PropertySource; @EnableConfigurationProperties -@PropertySource("pagination.properties") @ConfigurationProperties(prefix = "page-config") @Slf4j public record PageConfig( diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9541894..4d1a553 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -12,3 +12,6 @@ spring.h2.console.enabled=true spring.h2.console.path=/h2 ## spring.jpa.hibernate.ddl-auto=none +## BUG WAS HERE +page-config.default-page-num=0 +page-config.default-page-size=5 \ No newline at end of file diff --git a/src/main/resources/pagination.properties b/src/main/resources/pagination.properties index 7ba1262..e69de29 100644 --- a/src/main/resources/pagination.properties +++ b/src/main/resources/pagination.properties @@ -1,3 +0,0 @@ -## -page-config.default-page-num=0 -page-config.default-page-size=5 \ No newline at end of file From 73b787f40e7ccb9ee48a7792266cb19f17165b84 Mon Sep 17 00:00:00 2001 From: Mykhailo Ordyntsev <102993813+Maslyna@users.noreply.github.com> Date: Fri, 24 Mar 2023 20:47:23 +0100 Subject: [PATCH 07/28] user-story-1 (#35) * Updated project structure * Refactored code * Added profiles --- pom.xml | 1 - .../{PageConfig.java => PageProperties.java} | 9 +- .../com/provedcode/config/PropsConfig.java | 11 + .../com/provedcode/config/SecurityConfig.java | 1 + .../provedcode/talent/TalentController.java | 4 +- .../talent/mapper/TalentMapper.java | 27 +- .../talent/mapper/impl/TalentMapperImpl.java | 30 ++ .../talent/repo/TalentRepository.java | 8 +- .../repo/db/TalentEntityRepository.java | 14 +- .../talent/service/TalentService.java | 3 +- .../service/impl/TalentServiceImpl.java | 20 +- .../service/mock/TalentServiceMock.java | 21 +- src/main/resources/application-dev.properties | 2 + .../resources/application-prod.properties | 6 + src/main/resources/application.properties | 13 +- src/main/resources/data.sql | 290 ++++++++++++++++-- src/main/resources/pagination.properties | 3 + src/main/resources/schema.sql | 11 +- 18 files changed, 380 insertions(+), 94 deletions(-) rename src/main/java/com/provedcode/config/{PageConfig.java => PageProperties.java} (63%) create mode 100644 src/main/java/com/provedcode/config/PropsConfig.java create mode 100644 src/main/resources/application-dev.properties create mode 100644 src/main/resources/application-prod.properties diff --git a/pom.xml b/pom.xml index 9d74fdc..9cff8df 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ org.springframework.boot spring-boot-starter-web - org.springframework.boot spring-boot-devtools diff --git a/src/main/java/com/provedcode/config/PageConfig.java b/src/main/java/com/provedcode/config/PageProperties.java similarity index 63% rename from src/main/java/com/provedcode/config/PageConfig.java rename to src/main/java/com/provedcode/config/PageProperties.java index 1922716..48ab911 100644 --- a/src/main/java/com/provedcode/config/PageConfig.java +++ b/src/main/java/com/provedcode/config/PageProperties.java @@ -1,15 +1,20 @@ package com.provedcode.config; import jakarta.annotation.PostConstruct; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; -@EnableConfigurationProperties +@Validated @ConfigurationProperties(prefix = "page-config") @Slf4j -public record PageConfig( +public record PageProperties( int defaultPageNum, int defaultPageSize ) { diff --git a/src/main/java/com/provedcode/config/PropsConfig.java b/src/main/java/com/provedcode/config/PropsConfig.java new file mode 100644 index 0000000..5161d40 --- /dev/null +++ b/src/main/java/com/provedcode/config/PropsConfig.java @@ -0,0 +1,11 @@ +package com.provedcode.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@EnableConfigurationProperties(PageProperties.class) +@PropertySource("pagination.properties") +@Configuration +public class PropsConfig { +} diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index 3d2d942..819c2af 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -17,6 +17,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(c -> c + .requestMatchers("/actuator/health").permitAll() // for DevOps .requestMatchers(antMatcher("/h2/**")).permitAll() .requestMatchers(antMatcher("/api/**")).permitAll() .anyRequest().denyAll() diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index 0ca8e61..9abf1b4 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -4,10 +4,10 @@ import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; import lombok.AllArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; -import java.util.List; import java.util.Optional; @RestController @@ -22,7 +22,7 @@ FullTalentDTO getTalent(@PathVariable("id") long id) { @GetMapping("/api/talents") @ResponseStatus(HttpStatus.OK) - List getTalents(@RequestParam(value = "page") Optional page, + Page getTalents(@RequestParam(value = "page") Optional page, @RequestParam(value = "size") Optional size) { return talentService.getTalentsPage(page, size); } diff --git a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java index ca767a4..a3818a7 100644 --- a/src/main/java/com/provedcode/talent/mapper/TalentMapper.java +++ b/src/main/java/com/provedcode/talent/mapper/TalentMapper.java @@ -5,31 +5,8 @@ import com.provedcode.talent.model.entity.*; public interface TalentMapper { - default ShortTalentDTO talentToShortTalentDTO(Talent talent) { - return ShortTalentDTO.builder() - .id(talent.getId()) - .image(talent.getImage()) - .firstname(talent.getFirstName()) - .lastname(talent.getLastName()) - .specialization(talent.getSpecialization()) - .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) - .build(); - } - default FullTalentDTO talentToFullTalentDTO(Talent talent) { - return FullTalentDTO.builder() - .id(talent.getId()) - .firstname(talent.getFirstName()) - .lastname(talent.getLastName()) - .bio(talent.getTalentDescription().getBio()) - .additionalInfo(talent.getTalentDescription().getAdditionalInfo()) - .image(talent.getImage()) - .specialization(talent.getSpecialization()) - .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) - .contacts(talent.getTalentContacts().stream().map(TalentContact::getContact).toList()) - .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) - .attachedFiles(talent.getTalentAttachedFiles().stream().map(TalentAttachedFile::getAttachedFile).toList()) - .build(); - } + ShortTalentDTO talentToShortTalentDTO(Talent talent); + FullTalentDTO talentToFullTalentDTO(Talent talent); } diff --git a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java index 6dc6a03..90516ac 100644 --- a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java +++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java @@ -1,8 +1,38 @@ package com.provedcode.talent.mapper.impl; import com.provedcode.talent.mapper.TalentMapper; +import com.provedcode.talent.model.dto.FullTalentDTO; +import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.model.entity.*; import org.springframework.stereotype.Component; @Component public class TalentMapperImpl implements TalentMapper { + @Override + public ShortTalentDTO talentToShortTalentDTO(Talent talent) { + return ShortTalentDTO.builder() + .id(talent.getId()) + .image(talent.getImage()) + .firstname(talent.getFirstName()) + .lastname(talent.getLastName()) + .specialization(talent.getSpecialization()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .build(); + } + @Override + public FullTalentDTO talentToFullTalentDTO(Talent talent) { + return FullTalentDTO.builder() + .id(talent.getId()) + .firstname(talent.getFirstName()) + .lastname(talent.getLastName()) + .bio(talent.getTalentDescription().getBio()) + .additionalInfo(talent.getTalentDescription().getAdditionalInfo()) + .image(talent.getImage()) + .specialization(talent.getSpecialization()) + .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) + .contacts(talent.getTalentContacts().stream().map(TalentContact::getContact).toList()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .attachedFiles(talent.getTalentAttachedFiles().stream().map(TalentAttachedFile::getAttachedFile).toList()) + .build(); + } } diff --git a/src/main/java/com/provedcode/talent/repo/TalentRepository.java b/src/main/java/com/provedcode/talent/repo/TalentRepository.java index b2fe0de..c077eb8 100644 --- a/src/main/java/com/provedcode/talent/repo/TalentRepository.java +++ b/src/main/java/com/provedcode/talent/repo/TalentRepository.java @@ -1,16 +1,14 @@ package com.provedcode.talent.repo; import com.provedcode.talent.model.entity.Talent; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; -import java.util.List; import java.util.Optional; public interface TalentRepository { - List getTalentsPage(PageRequest page); + Page findAll(Pageable pageable); Optional findById(Long aLong); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java index aaa9688..e3fa4a9 100644 --- a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java +++ b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java @@ -2,27 +2,19 @@ import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.TalentRepository; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.annotation.Transactional; -import java.util.List; import java.util.Optional; public interface TalentEntityRepository extends JpaRepository, TalentRepository { @Transactional(readOnly = true) - default List getTalents() { - return findAll(); - } - + Page findAll(Pageable pageable); @Override @Transactional(readOnly = true) - default List getTalentsPage(PageRequest page) { - return findAll(page).stream().toList(); - } - - @Override Optional findById(Long aLong); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/service/TalentService.java b/src/main/java/com/provedcode/talent/service/TalentService.java index 3b1147a..e583ff2 100644 --- a/src/main/java/com/provedcode/talent/service/TalentService.java +++ b/src/main/java/com/provedcode/talent/service/TalentService.java @@ -2,13 +2,14 @@ import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; +import org.springframework.data.domain.Page; import java.util.List; import java.util.Optional; public interface TalentService { - List getTalentsPage(Optional page, Optional size); + Page getTalentsPage(Optional page, Optional size); FullTalentDTO getTalentById(long id); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java index 5f23955..af20a7f 100644 --- a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -1,6 +1,6 @@ package com.provedcode.talent.service.impl; -import com.provedcode.config.PageConfig; +import com.provedcode.config.PageProperties; import com.provedcode.talent.service.TalentService; import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.FullTalentDTO; @@ -9,12 +9,15 @@ import com.provedcode.talent.repo.TalentRepository; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.NOT_FOUND; @@ -25,20 +28,19 @@ public class TalentServiceImpl implements TalentService { TalentMapper talentMapper; TalentRepository talentRepository; - PageConfig pageConfig; + PageProperties pageProperties; @Override - public List getTalentsPage(Optional page, Optional size) { - if (page.orElse(pageConfig.defaultPageNum()) < 0) { + public Page getTalentsPage(Optional page, Optional size) { + if (page.orElse(pageProperties.defaultPageNum()) < 0) { throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); } - if (size.orElse(pageConfig.defaultPageSize()) <= 0) { + if (size.orElse(pageProperties.defaultPageSize()) <= 0) { throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); } - return talentRepository.getTalentsPage( - PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) - .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) - .toList(); + return talentRepository.findAll(PageRequest.of(page.orElse(pageProperties.defaultPageNum()), size.orElse(pageProperties.defaultPageSize()))) + .map(talentMapper::talentToShortTalentDTO); + } @Override diff --git a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java index 84ba719..b305f3a 100644 --- a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java +++ b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java @@ -1,6 +1,6 @@ package com.provedcode.talent.service.mock; -import com.provedcode.config.PageConfig; +import com.provedcode.config.PageProperties; import com.provedcode.talent.service.TalentService; import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.FullTalentDTO; @@ -9,10 +9,11 @@ import com.provedcode.talent.repo.TalentRepository; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.web.server.ResponseStatusException; -import java.util.List; import java.util.Optional; import static org.springframework.http.HttpStatus.BAD_REQUEST; @@ -24,20 +25,20 @@ public class TalentServiceMock implements TalentService { TalentMapper talentMapper; TalentRepository talentRepository; - PageConfig pageConfig; + PageProperties pageProperties; @Override - public List getTalentsPage(Optional page, Optional size) { - if (page.orElse(pageConfig.defaultPageNum()) < 0) { + public Page getTalentsPage(Optional page, Optional size) { + if (page.orElse(pageProperties.defaultPageNum()) < 0) { throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); } - if (size.orElse(pageConfig.defaultPageSize()) <= 0) { + if (size.orElse(pageProperties.defaultPageSize()) <= 0) { throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); } - return talentRepository.getTalentsPage( - PageRequest.of(page.orElse(pageConfig.defaultPageNum()), size.orElse(pageConfig.defaultPageSize()))) - .stream().map(i -> talentMapper.talentToShortTalentDTO(i)) - .toList(); + return new PageImpl<>(talentRepository.findAll( + PageRequest.of(page.orElse(pageProperties.defaultPageNum()), size.orElse(pageProperties.defaultPageSize()))) + .stream().map(talentMapper::talentToShortTalentDTO) + .toList()); } @Override diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..584e38f --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,2 @@ +spring.datasource.username=sa +spring.datasource.url=jdbc:h2:mem:../sampleDB diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..3eb12be --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,6 @@ +spring.datasource.username=${DB_LOGIN} +spring.datasource.password=${DB_PASSWORD} +spring.datasource.url=${DB_URL} +spring.jpa.hibernate.ddl-auto=none +spring.sql.init.mode=always +spring.sql.init.platform=h2 \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 4d1a553..55ff623 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,17 +1,12 @@ -server.port=28852 -management.endpoints.web.exposure.include=* -management.endpoint.shutdown.enabled=true +spring.profiles.active=${SPRING_PROFILES_ACTIVE:dev} +## +server.port=8080 ## spring.jackson.property-naming-strategy=SNAKE_CASE ## logging.level.cinema.controller=info ## spring.datasource.driverClassName=org.h2.Driver -spring.datasource.url=jdbc:h2:mem:/sampleDB spring.h2.console.enabled=true spring.h2.console.path=/h2 -## -spring.jpa.hibernate.ddl-auto=none -## BUG WAS HERE -page-config.default-page-num=0 -page-config.default-page-size=5 \ No newline at end of file +spring.jpa.hibernate.ddl-auto=update \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 5604d04..6b0f1ae 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,7 +1,7 @@ insert into talent (first_name, last_name, specialization, image) -values ('Serhii', 'Soloviov', 'Java-Developer', 'http://image'); +values ('Serhii', 'Soloviov', 'Java-Developer', 'https://i.pinimg.com/564x/e1/08/49/e10849923a8b2e85a7adf494ebd063e6.jpg'); insert into talent_description (talent_id, BIO, addition_info) -values((select id from talent order by id desc limit 1), 'Default bio', 'Default addition info'); +values((select id from talent order by id desc limit 1), 'Serhii Soloviov bio', 'Serhii Soloviov addition info'); insert into talent_link (talent_id, link) values ((select id from talent order by id desc limit 1), 'http://first_link'); insert into talent_link (talent_id, link) @@ -9,11 +9,13 @@ values ((select id from talent order by id desc limit 1), 'http://second_link'); insert into talent_link (talent_id, link) values ((select id from talent order by id desc limit 1), 'http://third_link'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'first_skill'); +values ((select id from talent order by id desc limit 1), 'Java Core'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'second_skill'); +values ((select id from talent order by id desc limit 1), 'Spring Core'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'third_skill'); +values ((select id from talent order by id desc limit 1), 'Spring boot'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'H2 Database'); insert into talent_contact (talent_id, contact) values ((select id from talent order by id desc limit 1), 'first_contact'); insert into talent_contact (talent_id, contact) @@ -28,7 +30,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'third_file'); insert into talent (first_name, last_name, specialization, image) -values ('Mykhailo', 'Ordyntsev', 'Java-Developer', 'http://MykhailoOrdyntsevImage'); +values ('Mykhailo', 'Ordyntsev', 'Java-Developer', 'https://i.pinimg.com/564x/c2/41/31/c24131fe00218467721ba5bacdf0a256.jpg'); insert into talent_description (talent_id, BIO, addition_info) values((select id from talent order by id desc limit 1), 'Mykhailo Ordyntsev bio', 'Mykhailo Ordyntsev addition info'); insert into talent_link (talent_id, link) @@ -38,11 +40,13 @@ values ((select id from talent order by id desc limit 1), 'http://MykhailoOrdynt insert into talent_link (talent_id, link) values ((select id from talent order by id desc limit 1), 'http://MykhailoOrdyntsev_third_link'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_first_skill'); +values ((select id from talent order by id desc limit 1), 'Java Core'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Hibernate'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_second_skill'); +values ((select id from talent order by id desc limit 1), 'Spring Boot'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_skill'); +values ((select id from talent order by id desc limit 1), 'Git'); insert into talent_contact (talent_id, contact) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_first_contact'); insert into talent_contact (talent_id, contact) @@ -57,7 +61,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file'); insert into talent (first_name, last_name, specialization, image) -values ('Denis', 'Boyko', 'Java-Developer', 'http://DenisBoykoImage'); +values ('Denis', 'Boyko', 'Java-Developer', 'https://i.pinimg.com/564x/2a/0c/08/2a0c08c421e253ca895c3fdc8c9e08d9.jpg'); insert into talent_description (talent_id, BIO, addition_info) values((select id from talent order by id desc limit 1), 'Denis Boyko bio', 'Denis Boyko addition info'); insert into talent_link (talent_id, link) @@ -67,11 +71,11 @@ values ((select id from talent order by id desc limit 1), 'http://DenisBoyko_sec insert into talent_link (talent_id, link) values ((select id from talent order by id desc limit 1), 'http://DenisBoyko_third_link'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'DenisBoyko_first_skill'); +values ((select id from talent order by id desc limit 1), 'Java Core'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_skill'); +values ((select id from talent order by id desc limit 1), 'Spring Security'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_skill'); +values ((select id from talent order by id desc limit 1), 'Spring Core'); insert into talent_contact (talent_id, contact) values ((select id from talent order by id desc limit 1), 'DenisBoyko_first_contact'); insert into talent_contact (talent_id, contact) @@ -86,7 +90,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_file'); insert into talent (first_name, last_name, specialization, image) -values ('Ihor', 'Schurenko', 'Java-Developer', 'http://IhorShchurenkoImage'); +values ('Ihor', 'Schurenko', 'Java-Developer', 'https://i.pinimg.com/564x/e1/11/2f/e1112f0b7b63644dc3e313084936dedb.jpg'); insert into talent_description (talent_id, BIO, addition_info) values((select id from talent order by id desc limit 1), 'Ihor Shchurenko bio', 'Ihor Shchurenko addition info'); insert into talent_link (talent_id, link) @@ -96,11 +100,9 @@ values ((select id from talent order by id desc limit 1), 'http://IhorShchurenko insert into talent_link (talent_id, link) values ((select id from talent order by id desc limit 1), 'http://IhorShchurenko_third_link'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'IhorShchurenko_first_skill'); -insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_skill'); +values ((select id from talent order by id desc limit 1), 'Java Core'); insert into talent_skill (talent_id, skill) -values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_skill'); +values ((select id from talent order by id desc limit 1), 'REST API'); insert into talent_contact (talent_id, contact) values ((select id from talent order by id desc limit 1), 'IhorShchurenko_first_contact'); insert into talent_contact (talent_id, contact) @@ -113,3 +115,255 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_file'); insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_file'); +insert into talent (first_name, last_name, specialization, image) +values ('Dmytro', 'Uzun', 'Dev-Ops', 'https://i.pinimg.com/564x/1c/af/87/1caf8771ef3edf351f6f2bf6f1c0a276.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Dmytro Uzun bio', 'Dmytro Uzun addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DmytroUzun_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DmytroUzun_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DmytroUzun_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Git'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Docker'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Mentor'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DmytroUzun_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DmytroUzun_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DmytroUzun_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DmytroUzun_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DmytroUzun_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DmytroUzun_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Viktor', 'Voloshko', 'Dev-Ops', 'https://i.pinimg.com/564x/a9/51/ab/a951ab682413b89617235e65564c1e5e.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Viktor Voloshko bio', 'Viktor Voloshko addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://ViktorVoloshko_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://ViktorVoloshko_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://ViktorVoloshko_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Git'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Docker'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Olha', 'Moiseienko', 'QA', 'https://i.pinimg.com/564x/6d/9d/43/6d9d437baf4db114c047d927307beb84.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Olha Moiseienko bio', 'Olha Moiseienko addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://OlhaMoiseienko_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://OlhaMoiseienko_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://OlhaMoiseienko_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Git'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Jira'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'QA'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko _third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Maxim', 'Kiyashko', 'QA', 'https://i.pinimg.com/564x/80/2d/58/802d58b0302985f9486893d499d3634d.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Maxim Kiyashko', 'Ihor Shchurenko addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://MaximKiyashko_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://MaximKiyashko_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://MaximKiyashko_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Git'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'QA'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Nikolaiev', 'Oleksii', 'QA', 'https://i.pinimg.com/564x/54/d1/0d/54d10dfce64afefabc9fbbce5de82c87.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Nikolaiev Oleksii bio', 'Nikolaiev Oleksii addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://NikolaievOleksii_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://NikolaievOleksii_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://NikolaievOleksii_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'QA'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Git'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_third_skill'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Artem', 'Lytvynenko', 'QA', 'https://i.pinimg.com/564x/87/63/55/87635509c5fa7ee496ec351fa7e67eaa.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Artem Lytvynenko bio', 'Artem Lytvynenko addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://ArtemLytvynenko_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://ArtemLytvynenko_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://ArtemLytvynenko_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'QA'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Git'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Daniil', 'Yevtukhov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/fe/b1/37/feb137d88a3d1c8fb28796db6cbc576f.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Daniil Yevtukhov bio', 'Daniil Yevtukhov addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DaniilYevtukhov_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DaniilYevtukhov_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://DaniilYevtukhov_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'JavaScript Core'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'React'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Ruslan', 'Morozov', 'Java-Script-Developer', 'https://i.pinimg.com/736x/36/ae/0e/36ae0ea4aad656f7c3d3175bc33b8399.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Ruslan Morozov bio', 'Ruslan Morozov addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://RuslanMorozov_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://RuslanMorozov_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://RuslanMorozov_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'JavaScript Core'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'React'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Node.js'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov_third_file'); + +insert into talent (first_name, last_name, specialization, image) +values ('Ihor', 'Kopieichykov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/0d/f0/83/0df083121bac75f64e3d93c7c5682d04.jpg'); +insert into talent_description (talent_id, BIO, addition_info) +values((select id from talent order by id desc limit 1), 'Ihor Kopieichykov bio', 'Ihor Kopieichykov addition info'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://IhorKopieichykov_first_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://IhorKopieichykov_second_link'); +insert into talent_link (talent_id, link) +values ((select id from talent order by id desc limit 1), 'http://IhorKopieichykov_third_link'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'JavaScript Core'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'React'); +insert into talent_skill (talent_id, skill) +values ((select id from talent order by id desc limit 1), 'Angular'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_first_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_second_contact'); +insert into talent_contact (talent_id, contact) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_contact'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_first_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_second_file'); +insert into talent_attached_file (talent_id, attached_file) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_file'); \ No newline at end of file diff --git a/src/main/resources/pagination.properties b/src/main/resources/pagination.properties index e69de29..81dce8f 100644 --- a/src/main/resources/pagination.properties +++ b/src/main/resources/pagination.properties @@ -0,0 +1,3 @@ +## DEFAULT PAGE VALUES +page-config.default-page-num=0 +page-config.default-page-size=5 \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 06f6c86..05c6a9e 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,4 +1,13 @@ ---talent tables-- +DROP TABLE IF EXISTS talent CASCADE ; +DROP TABLE IF EXISTS talent_description CASCADE ; +DROP TABLE IF EXISTS talent_link CASCADE ; +DROP TABLE IF EXISTS talent_contact CASCADE ; +DROP TABLE IF EXISTS talent_attached_file CASCADE ; +DROP TABLE IF EXISTS talent_skill CASCADE ; +DROP TABLE IF EXISTS user_authority CASCADE ; +DROP TABLE IF EXISTS user_info CASCADE ; +DROP TABLE IF EXISTS authority CASCADE ; + CREATE TABLE talent ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, first_name VARCHAR(20) NOT NULL, From a4145afb6c5c0dc417cb13dfc45eeedf66a8c039 Mon Sep 17 00:00:00 2001 From: Mykhailo Ordyntsev <102993813+Maslyna@users.noreply.github.com> Date: Sat, 25 Mar 2023 14:15:19 +0100 Subject: [PATCH 08/28] bugfix (#36) bugfix: java.io.FileNotFoundException: Could not open ServletContext resource [/pagination.properties] --- src/main/java/com/provedcode/config/PropsConfig.java | 4 +--- src/main/resources/application.properties | 5 ++++- src/main/resources/pagination.properties | 2 -- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/provedcode/config/PropsConfig.java b/src/main/java/com/provedcode/config/PropsConfig.java index 5161d40..2cfe88e 100644 --- a/src/main/java/com/provedcode/config/PropsConfig.java +++ b/src/main/java/com/provedcode/config/PropsConfig.java @@ -4,8 +4,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; -@EnableConfigurationProperties(PageProperties.class) -@PropertySource("pagination.properties") -@Configuration + public class PropsConfig { } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 55ff623..ae6aba0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,7 @@ logging.level.cinema.controller=info spring.datasource.driverClassName=org.h2.Driver spring.h2.console.enabled=true spring.h2.console.path=/h2 -spring.jpa.hibernate.ddl-auto=update \ No newline at end of file +spring.jpa.hibernate.ddl-auto=update +##DEFAULT PAGE PROPS +page-config.default-page-num=0 +page-config.default-page-size=5 \ No newline at end of file diff --git a/src/main/resources/pagination.properties b/src/main/resources/pagination.properties index 81dce8f..7cbaa30 100644 --- a/src/main/resources/pagination.properties +++ b/src/main/resources/pagination.properties @@ -1,3 +1 @@ ## DEFAULT PAGE VALUES -page-config.default-page-num=0 -page-config.default-page-size=5 \ No newline at end of file From 8b174a6c135edcfe7ef9ced48f65f59e3a2ffb1c Mon Sep 17 00:00:00 2001 From: Ren <75202059+LordRenDS@users.noreply.github.com> Date: Sun, 26 Mar 2023 21:20:41 +0300 Subject: [PATCH 09/28] Add WebConfig (#48) --- .../java/com/provedcode/config/WebConfig.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/main/java/com/provedcode/config/WebConfig.java diff --git a/src/main/java/com/provedcode/config/WebConfig.java b/src/main/java/com/provedcode/config/WebConfig.java new file mode 100644 index 0000000..d0485c5 --- /dev/null +++ b/src/main/java/com/provedcode/config/WebConfig.java @@ -0,0 +1,15 @@ +package com.provedcode.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@EnableWebMvc +public class WebConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**"); + } +} From 520855d81912ce3c756a38227f18139ea24b7313 Mon Sep 17 00:00:00 2001 From: Ren <75202059+LordRenDS@users.noreply.github.com> Date: Sun, 26 Mar 2023 21:33:36 +0300 Subject: [PATCH 10/28] Add @CrossOrigin to TalentController (#50) --- src/main/java/com/provedcode/talent/TalentController.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index 9abf1b4..db226e9 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -1,8 +1,8 @@ package com.provedcode.talent; -import com.provedcode.talent.service.TalentService; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.talent.service.TalentService; import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; @@ -12,6 +12,8 @@ @RestController @AllArgsConstructor +@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, + RequestMethod.DELETE}) public class TalentController { TalentService talentService; From f34082539c9f72aa3c80e21131ec0338fe4bf821 Mon Sep 17 00:00:00 2001 From: Ren <75202059+LordRenDS@users.noreply.github.com> Date: Sun, 26 Mar 2023 21:42:43 +0300 Subject: [PATCH 11/28] Delete WebConfig (#52) --- .../java/com/provedcode/config/WebConfig.java | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 src/main/java/com/provedcode/config/WebConfig.java diff --git a/src/main/java/com/provedcode/config/WebConfig.java b/src/main/java/com/provedcode/config/WebConfig.java deleted file mode 100644 index d0485c5..0000000 --- a/src/main/java/com/provedcode/config/WebConfig.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.provedcode.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@EnableWebMvc -public class WebConfig implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**"); - } -} From efaac0ba5467daa0fa3616145e991fc2561fbefd Mon Sep 17 00:00:00 2001 From: Maslyna Date: Sun, 26 Mar 2023 22:39:12 +0200 Subject: [PATCH 12/28] data.sql Update --- .../provedcode/talent/TalentController.java | 12 ++- .../service/impl/TalentServiceImpl.java | 3 - src/main/resources/data.sql | 95 ++++++++++++++++++- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index db226e9..f1ad5d4 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -4,28 +4,32 @@ import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.service.TalentService; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; +import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.web.bind.annotation.*; import java.util.Optional; +@Slf4j @RestController @AllArgsConstructor -@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, - RequestMethod.DELETE}) +@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE}) +@RequestMapping("/api") public class TalentController { TalentService talentService; - @GetMapping("/api/talents/{id}") + @GetMapping("/talents/{id}") FullTalentDTO getTalent(@PathVariable("id") long id) { return talentService.getTalentById(id); } - @GetMapping("/api/talents") + @GetMapping("/talents") @ResponseStatus(HttpStatus.OK) Page getTalents(@RequestParam(value = "page") Optional page, @RequestParam(value = "size") Optional size) { return talentService.getTalentsPage(page, size); } + } diff --git a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java index af20a7f..df92748 100644 --- a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -10,14 +10,11 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; -import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.NOT_FOUND; diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 6b0f1ae..9a05fa0 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,3 +1,12 @@ +insert into authority (id, authority) +VALUES (1, 'ROLE_TALENT'); +-- FOR USER AUTHORITY +-- SELECT USER_INFO.ID , LOGIN , PASSWORD, USER_ID , AUTHORITY FROM +-- USER_INFO +-- JOIN USER_AUTHORITY ON USER_ID = USER_INFO.ID +-- JOIN AUTHORITY ON AUTHORITY.ID = AUTHORITY_ID + + insert into talent (first_name, last_name, specialization, image) values ('Serhii', 'Soloviov', 'Java-Developer', 'https://i.pinimg.com/564x/e1/08/49/e10849923a8b2e85a7adf494ebd063e6.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -29,6 +38,13 @@ values ((select id from talent order by id desc limit 1), 'second_file'); insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Mykhailo', 'Ordyntsev', 'Java-Developer', 'https://i.pinimg.com/564x/c2/41/31/c24131fe00218467721ba5bacdf0a256.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -60,6 +76,13 @@ values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_sec insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Denis', 'Boyko', 'Java-Developer', 'https://i.pinimg.com/564x/2a/0c/08/2a0c08c421e253ca895c3fdc8c9e08d9.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -89,6 +112,13 @@ values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_fil insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'DenisBoyko', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Ihor', 'Schurenko', 'Java-Developer', 'https://i.pinimg.com/564x/e1/11/2f/e1112f0b7b63644dc3e313084936dedb.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -144,6 +174,13 @@ values ((select id from talent order by id desc limit 1), 'DmytroUzun_second_fil insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DmytroUzun_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'DmytroUzun', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Viktor', 'Voloshko', 'Dev-Ops', 'https://i.pinimg.com/564x/a9/51/ab/a951ab682413b89617235e65564c1e5e.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -171,6 +208,13 @@ values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_second insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'ViktorVoloshko', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Olha', 'Moiseienko', 'QA', 'https://i.pinimg.com/564x/6d/9d/43/6d9d437baf4db114c047d927307beb84.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -200,6 +244,13 @@ values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_second insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko _third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Maxim', 'Kiyashko', 'QA', 'https://i.pinimg.com/564x/80/2d/58/802d58b0302985f9486893d499d3634d.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -227,6 +278,13 @@ values ((select id from talent order by id desc limit 1), 'MaximKiyashko_second_ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MaximKiyashko_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'MaximKiyashko', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Nikolaiev', 'Oleksii', 'QA', 'https://i.pinimg.com/564x/54/d1/0d/54d10dfce64afefabc9fbbce5de82c87.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -256,6 +314,13 @@ values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_seco insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Artem', 'Lytvynenko', 'QA', 'https://i.pinimg.com/564x/87/63/55/87635509c5fa7ee496ec351fa7e67eaa.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -283,6 +348,13 @@ values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_secon insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Daniil', 'Yevtukhov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/fe/b1/37/feb137d88a3d1c8fb28796db6cbc576f.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -310,6 +382,13 @@ values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_secon insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Ruslan', 'Morozov', 'Java-Script-Developer', 'https://i.pinimg.com/736x/36/ae/0e/36ae0ea4aad656f7c3d3175bc33b8399.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -339,6 +418,13 @@ values ((select id from talent order by id desc limit 1), 'RuslanMorozov_second_ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'RuslanMorozov_third_file'); +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'RuslanMorozov', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); + insert into talent (first_name, last_name, specialization, image) values ('Ihor', 'Kopieichykov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/0d/f0/83/0df083121bac75f64e3d93c7c5682d04.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -366,4 +452,11 @@ values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_firs insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_second_file'); insert into talent_attached_file (talent_id, attached_file) -values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_file'); \ No newline at end of file +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_file'); + +insert into user_info (id, login, password) +values ((select id from talent order by id desc limit 1), 'IhorKopieichykov', 'password'); +insert into user_authority (id, user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select id from user_info order by id desc limit 1), + (select authority.id from authority order by id desc limit 1)); From 4c158482a0d44434b695252a45e78a9dbfb4811c Mon Sep 17 00:00:00 2001 From: Ren Date: Mon, 27 Mar 2023 00:37:58 +0300 Subject: [PATCH 13/28] Edit data.sql and schema.sql --- src/main/resources/data.sql | 93 +++++++++++++++++------------------ src/main/resources/schema.sql | 39 ++++++++------- 2 files changed, 64 insertions(+), 68 deletions(-) diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 9a05fa0..7e1c2fb 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -1,5 +1,5 @@ insert into authority (id, authority) -VALUES (1, 'ROLE_TALENT'); +values (1, 'ROLE_TALENT'); -- FOR USER AUTHORITY -- SELECT USER_INFO.ID , LOGIN , PASSWORD, USER_ID , AUTHORITY FROM -- USER_INFO @@ -38,12 +38,11 @@ values ((select id from talent order by id desc limit 1), 'second_file'); insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Mykhailo', 'Ordyntsev', 'Java-Developer', 'https://i.pinimg.com/564x/c2/41/31/c24131fe00218467721ba5bacdf0a256.jpg'); @@ -76,12 +75,11 @@ values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_sec insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Denis', 'Boyko', 'Java-Developer', 'https://i.pinimg.com/564x/2a/0c/08/2a0c08c421e253ca895c3fdc8c9e08d9.jpg'); @@ -112,12 +110,11 @@ values ((select id from talent order by id desc limit 1), 'DenisBoyko_second_fil insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DenisBoyko_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'DenisBoyko', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Ihor', 'Schurenko', 'Java-Developer', 'https://i.pinimg.com/564x/e1/11/2f/e1112f0b7b63644dc3e313084936dedb.jpg'); @@ -145,6 +142,13 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'IhorShchurenko_second_file'); insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'IhorShchurenko_third_file'); + +insert into user_info (user_id, login, password) +values ((select id from talent order by id desc limit 1), 'DmytroUzun', 'password'); +insert into user_authority (user_id, authority_id) +values ((select id from user_info order by id desc limit 1), + (select authority.id from authority where id = 1)); + insert into talent (first_name, last_name, specialization, image) values ('Dmytro', 'Uzun', 'Dev-Ops', 'https://i.pinimg.com/564x/1c/af/87/1caf8771ef3edf351f6f2bf6f1c0a276.jpg'); insert into talent_description (talent_id, BIO, addition_info) @@ -174,12 +178,11 @@ values ((select id from talent order by id desc limit 1), 'DmytroUzun_second_fil insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DmytroUzun_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'DmytroUzun', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Viktor', 'Voloshko', 'Dev-Ops', 'https://i.pinimg.com/564x/a9/51/ab/a951ab682413b89617235e65564c1e5e.jpg'); @@ -208,12 +211,11 @@ values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_second insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'ViktorVoloshko_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'ViktorVoloshko', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Olha', 'Moiseienko', 'QA', 'https://i.pinimg.com/564x/6d/9d/43/6d9d437baf4db114c047d927307beb84.jpg'); @@ -244,12 +246,11 @@ values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko_second insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko _third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'OlhaMoiseienko', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Maxim', 'Kiyashko', 'QA', 'https://i.pinimg.com/564x/80/2d/58/802d58b0302985f9486893d499d3634d.jpg'); @@ -278,12 +279,11 @@ values ((select id from talent order by id desc limit 1), 'MaximKiyashko_second_ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MaximKiyashko_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'MaximKiyashko', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Nikolaiev', 'Oleksii', 'QA', 'https://i.pinimg.com/564x/54/d1/0d/54d10dfce64afefabc9fbbce5de82c87.jpg'); @@ -314,12 +314,11 @@ values ((select id from talent order by id desc limit 1), 'NikolaievOleksii_seco insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'NikolaievOleksiio', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Artem', 'Lytvynenko', 'QA', 'https://i.pinimg.com/564x/87/63/55/87635509c5fa7ee496ec351fa7e67eaa.jpg'); @@ -348,12 +347,11 @@ values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_secon insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'ArtemLytvynenko', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Daniil', 'Yevtukhov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/fe/b1/37/feb137d88a3d1c8fb28796db6cbc576f.jpg'); @@ -382,12 +380,11 @@ values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_secon insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'DaniilYevtukhov', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Ruslan', 'Morozov', 'Java-Script-Developer', 'https://i.pinimg.com/736x/36/ae/0e/36ae0ea4aad656f7c3d3175bc33b8399.jpg'); @@ -418,12 +415,11 @@ values ((select id from talent order by id desc limit 1), 'RuslanMorozov_second_ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'RuslanMorozov_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'RuslanMorozov', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); insert into talent (first_name, last_name, specialization, image) values ('Ihor', 'Kopieichykov', 'Java-Script-Developer', 'https://i.pinimg.com/564x/0d/f0/83/0df083121bac75f64e3d93c7c5682d04.jpg'); @@ -454,9 +450,8 @@ values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_seco insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'IhorKopieichykov_third_file'); -insert into user_info (id, login, password) +insert into user_info (user_id, login, password) values ((select id from talent order by id desc limit 1), 'IhorKopieichykov', 'password'); -insert into user_authority (id, user_id, authority_id) +insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), - (select id from user_info order by id desc limit 1), - (select authority.id from authority order by id desc limit 1)); + (select authority.id from authority where id = 1)); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 05c6a9e..42570c3 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -1,14 +1,14 @@ -DROP TABLE IF EXISTS talent CASCADE ; -DROP TABLE IF EXISTS talent_description CASCADE ; -DROP TABLE IF EXISTS talent_link CASCADE ; -DROP TABLE IF EXISTS talent_contact CASCADE ; -DROP TABLE IF EXISTS talent_attached_file CASCADE ; -DROP TABLE IF EXISTS talent_skill CASCADE ; -DROP TABLE IF EXISTS user_authority CASCADE ; -DROP TABLE IF EXISTS user_info CASCADE ; -DROP TABLE IF EXISTS authority CASCADE ; - -CREATE TABLE talent ( +drop table IF EXISTS talent CASCADE ; +drop table IF EXISTS talent_description CASCADE ; +drop table IF EXISTS talent_link CASCADE ; +drop table IF EXISTS talent_contact CASCADE ; +drop table IF EXISTS talent_attached_file CASCADE ; +drop table IF EXISTS talent_skill CASCADE ; +drop table IF EXISTS user_authority CASCADE ; +drop table IF EXISTS user_info CASCADE ; +drop table IF EXISTS authority CASCADE ; + +create TABLE talent ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, first_name VARCHAR(20) NOT NULL, last_name VARCHAR(20) NOT NULL, @@ -65,27 +65,28 @@ alter table talent_attached_file add CONSTRAINT FK_TALENT_ATTACHED_FILE_ON_TALEN --user tables-- CREATE TABLE user_info ( - id BIGINT NOT NULL, - login VARCHAR(100), - password VARCHAR(255), + id BIGINT AUTO_INCREMENT NOT NULL, + user_id BIGINT NOT NULL, + login VARCHAR(100) NOT NULL, + password VARCHAR(255) NOT NULL, CONSTRAINT pk_user_info PRIMARY KEY (id) ); -ALTER TABLE user_info ADD CONSTRAINT FK_USER_INFO_ON_ID FOREIGN KEY (id) REFERENCES talent (id); +ALTER TABLE user_info ADD CONSTRAINT FK_USER_INFO_ON_USER FOREIGN KEY (user_id) REFERENCES talent (id); -CREATE TABLE authority ( +create TABLE authority ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, authority VARCHAR(50) NOT NULL, CONSTRAINT pk_authority PRIMARY KEY (id) ); -CREATE TABLE user_authority ( +create TABLE user_authority ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, user_id BIGINT, authority_id BIGINT, CONSTRAINT pk_user_authority PRIMARY KEY (id) ); -ALTER TABLE user_authority ADD CONSTRAINT FK_USER_AUTHORITY_ON_AUTHORITY FOREIGN KEY (authority_id) REFERENCES authority (id); +alter table user_authority add CONSTRAINT FK_USER_AUTHORITY_ON_AUTHORITY FOREIGN KEY (authority_id) REFERENCES authority (id); -ALTER TABLE user_authority ADD CONSTRAINT FK_USER_AUTHORITY_ON_USER FOREIGN KEY (user_id) REFERENCES user_info (id); \ No newline at end of file +alter table user_authority add CONSTRAINT FK_USER_AUTHORITY_ON_USER FOREIGN KEY (user_id) REFERENCES user_info (id); \ No newline at end of file From a21a15a1fe9542366b6e1966313ff84a369957e7 Mon Sep 17 00:00:00 2001 From: Ren Date: Mon, 27 Mar 2023 00:39:02 +0300 Subject: [PATCH 14/28] Edit UserInfo: add user_id and @NotNull, @NotEmpty validations --- .../com/provedcode/user/model/entity/UserInfo.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/provedcode/user/model/entity/UserInfo.java b/src/main/java/com/provedcode/user/model/entity/UserInfo.java index 27f9a8e..dcc83ec 100644 --- a/src/main/java/com/provedcode/user/model/entity/UserInfo.java +++ b/src/main/java/com/provedcode/user/model/entity/UserInfo.java @@ -2,6 +2,8 @@ import com.provedcode.talent.model.entity.Talent; import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @@ -14,16 +16,23 @@ @Entity @Table(name = "user_info") public class UserInfo { - @NotNull @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; + @NotNull + @Column(name = "user_id") + private Long userId; + @NotEmpty + @NotNull @Column(name = "login", length = 100) private String login; + @NotEmpty + @NotNull @Column(name = "password") private String password; @OneToOne(orphanRemoval = true) - @JoinColumn(name = "id") + @JoinColumn(name = "user_id", insertable = false, updatable = false) private Talent talent; @OneToMany(mappedBy = "userInfo", orphanRemoval = true) private Set userAuthorities = new LinkedHashSet<>(); From e220c3221038c5b7fb875156330690b58897ca6c Mon Sep 17 00:00:00 2001 From: Ren Date: Mon, 27 Mar 2023 01:18:30 +0300 Subject: [PATCH 15/28] Edit UserInfo: add @Builder, @AllArgsConstructor, @NoArgsConstructor --- .../talent/service/impl/TalentServiceImpl.java | 9 +++++---- .../java/com/provedcode/user/model/entity/UserInfo.java | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java index df92748..8512d29 100644 --- a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -1,12 +1,12 @@ package com.provedcode.talent.service.impl; import com.provedcode.config.PageProperties; -import com.provedcode.talent.service.TalentService; import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.TalentRepository; +import com.provedcode.talent.service.TalentService; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -35,15 +35,16 @@ public Page getTalentsPage(Optional page, Optional talent = talentRepository.findById(id); - if (talent.isEmpty()){ + if (talent.isEmpty()) { throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); } return talentMapper.talentToFullTalentDTO(talent.get()); diff --git a/src/main/java/com/provedcode/user/model/entity/UserInfo.java b/src/main/java/com/provedcode/user/model/entity/UserInfo.java index dcc83ec..bdbfe03 100644 --- a/src/main/java/com/provedcode/user/model/entity/UserInfo.java +++ b/src/main/java/com/provedcode/user/model/entity/UserInfo.java @@ -5,12 +5,14 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import java.util.LinkedHashSet; import java.util.Set; +@Builder +@AllArgsConstructor +@NoArgsConstructor @Getter @Setter @Entity From 41fad81156eb55a8ac4812eb27f497ec6e86308a Mon Sep 17 00:00:00 2001 From: Maslyna Date: Mon, 27 Mar 2023 12:47:56 +0200 Subject: [PATCH 16/28] commit 1 --- pom.xml | 2 +- .../com/provedcode/config/SecurityConfig.java | 80 ++++++++++++++++++- .../provedcode/talent/TalentController.java | 3 +- .../controllers/AuthenticationController.java | 56 +++++++++++++ .../user/mapper/UserInfoMapper.java | 8 ++ .../user/mapper/UserInfoMapperImpl.java | 23 ++++++ .../user/model/entity/Authority.java | 3 - .../user/repo/UserInfoRepository.java | 4 + 8 files changed, 171 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/provedcode/user/controllers/AuthenticationController.java create mode 100644 src/main/java/com/provedcode/user/mapper/UserInfoMapper.java create mode 100644 src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java diff --git a/pom.xml b/pom.xml index 9cff8df..56549a5 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ org.springframework.boot - spring-boot-starter-oauth2-client + spring-boot-starter-oauth2-resource-server org.springframework.boot diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index 819c2af..e0db777 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -1,12 +1,36 @@ package com.provedcode.config; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.provedcode.user.mapper.UserInfoMapper; +import com.provedcode.user.repo.UserInfoRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; +import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.web.SecurityFilterChain; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; + import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; @@ -18,12 +42,62 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(c -> c .requestMatchers("/actuator/health").permitAll() // for DevOps + .requestMatchers("/api/login").permitAll() .requestMatchers(antMatcher("/h2/**")).permitAll() - .requestMatchers(antMatcher("/api/**")).permitAll() - .anyRequest().denyAll() + .requestMatchers(antMatcher("/api/talents")).permitAll() + .anyRequest().permitAll() ); - http.csrf().disable().headers().frameOptions().disable(); + + http.httpBasic(Customizer.withDefaults()); + http.csrf().disable().headers().disable(); + http.sessionManagement().sessionCreationPolicy(STATELESS); + + http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) + .exceptionHandling(c -> c + .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) + .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) + ); + return http.build(); } + + @Bean + UserDetailsService userDetailsService ( + UserInfoRepository repository, + UserInfoMapper mapper + ) { + return login -> repository.findByLogin(login) + .map(mapper::toUserDetails) + .orElseThrow(() -> new UsernameNotFoundException(login + " not found")); + } + + @Bean + public KeyPair keyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + return keyPairGenerator.generateKeyPair(); + } + + @Bean + JwtDecoder jwtDecoder(KeyPair keyPair) { + return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build(); + } + + @Bean + JwtEncoder jwtEncoder(KeyPair keyPair) { + var jwk = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic()).privateKey(keyPair.getPrivate()).build(); + var jwkSet = new ImmutableJWKSet<>(new JWKSet(jwk)); + return new NimbusJwtEncoder(jwkSet); + } + + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + grantedAuthoritiesConverter.setAuthorityPrefix(""); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } } diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index f1ad5d4..a372bdb 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -7,7 +7,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; -import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.Optional; @@ -20,6 +20,7 @@ public class TalentController { TalentService talentService; + @PreAuthorize("hasRole('TALENT')") @GetMapping("/talents/{id}") FullTalentDTO getTalent(@PathVariable("id") long id) { return talentService.getTalentById(id); diff --git a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java new file mode 100644 index 0000000..719cbe1 --- /dev/null +++ b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java @@ -0,0 +1,56 @@ +package com.provedcode.user.controllers; + +import com.provedcode.user.model.entity.UserInfo; +import com.provedcode.user.repo.UserInfoRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.Instant; +import java.util.List; +import java.util.stream.Collectors; + +import static java.time.temporal.ChronoUnit.MINUTES; + +@RestController +@AllArgsConstructor +@Slf4j +public class AuthenticationController { + JwtEncoder jwtEncoder; + UserInfoRepository repository; + + @GetMapping("/test") + List test() { + return repository.findAll().stream().map(i -> i.getPassword()).toList(); + } + + @PostMapping("/api/login") + String login(Authentication authentication) { + log.info("=== POST /login === auth.name = {}", authentication.getName()); + log.info("=== POST /login === auth = {}", authentication); + var now = Instant.now(); + var claims = JwtClaimsSet.builder() + .issuer("self") + .issuedAt(now) + .expiresAt(now.plus(5, MINUTES)) + .subject(authentication.getName()) + .claim("scope", createScope(authentication)) + .build(); + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } + + + private String createScope(Authentication authentication) { + return authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(" ")); + } +} diff --git a/src/main/java/com/provedcode/user/mapper/UserInfoMapper.java b/src/main/java/com/provedcode/user/mapper/UserInfoMapper.java new file mode 100644 index 0000000..d7138b4 --- /dev/null +++ b/src/main/java/com/provedcode/user/mapper/UserInfoMapper.java @@ -0,0 +1,8 @@ +package com.provedcode.user.mapper; + +import com.provedcode.user.model.entity.UserInfo; +import org.springframework.security.core.userdetails.UserDetails; + +public interface UserInfoMapper { + UserDetails toUserDetails(UserInfo user); +} diff --git a/src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java b/src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java new file mode 100644 index 0000000..fc2db37 --- /dev/null +++ b/src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java @@ -0,0 +1,23 @@ +package com.provedcode.user.mapper; + +import com.provedcode.user.model.entity.UserInfo; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +public class UserInfoMapperImpl implements UserInfoMapper { + @Override + public UserDetails toUserDetails(UserInfo user) { + return User.withUsername(user.getLogin()) + .password(user.getPassword()) + .authorities(user.getUserAuthorities() + .stream() + .map(i -> new SimpleGrantedAuthority( + i.getAuthority() + .getAuthority())) + .toList()) + .build(); + } +} diff --git a/src/main/java/com/provedcode/user/model/entity/Authority.java b/src/main/java/com/provedcode/user/model/entity/Authority.java index 384c35a..c722696 100644 --- a/src/main/java/com/provedcode/user/model/entity/Authority.java +++ b/src/main/java/com/provedcode/user/model/entity/Authority.java @@ -6,9 +6,6 @@ import lombok.Getter; import lombok.Setter; -import java.util.LinkedHashSet; -import java.util.Set; - @Getter @Setter @Entity diff --git a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java index bb340ee..1e78951 100644 --- a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java +++ b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java @@ -3,5 +3,9 @@ import com.provedcode.user.model.entity.UserInfo; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface UserInfoRepository extends JpaRepository { + Optional findByLogin(String login); + } \ No newline at end of file From dd9668213f5450ba33ee65f077151954cb24fc70 Mon Sep 17 00:00:00 2001 From: Maslyna Date: Mon, 27 Mar 2023 13:16:30 +0200 Subject: [PATCH 17/28] commit 2 --- src/main/java/com/provedcode/config/SecurityConfig.java | 7 ++++++- .../user/controllers/AuthenticationController.java | 3 ++- src/main/resources/data.sql | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index e0db777..f2dc12e 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -45,7 +45,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/api/login").permitAll() .requestMatchers(antMatcher("/h2/**")).permitAll() .requestMatchers(antMatcher("/api/talents")).permitAll() - .anyRequest().permitAll() + .anyRequest().authenticated() ); http.httpBasic(Customizer.withDefaults()); @@ -72,6 +72,11 @@ UserDetailsService userDetailsService ( .orElseThrow(() -> new UsernameNotFoundException(login + " not found")); } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + @Bean public KeyPair keyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); diff --git a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java index 719cbe1..48e1876 100644 --- a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java @@ -26,10 +26,11 @@ public class AuthenticationController { JwtEncoder jwtEncoder; UserInfoRepository repository; + PasswordEncoder passwordEncoder; @GetMapping("/test") List test() { - return repository.findAll().stream().map(i -> i.getPassword()).toList(); + return repository.findAll().stream().map(i -> i.getPassword() + " : " + passwordEncoder.encode(i.getPassword())).toList(); } @PostMapping("/api/login") diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index 7e1c2fb..a92ba47 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -39,7 +39,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'third_file'); insert into user_info (user_id, login, password) -values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', 'password'); +values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', '$2a$10$1Np2nFzR58WBHCPEBB8vEuePZTtZFA53A4oHRADiviDVkTyt2Olau'); insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), (select authority.id from authority where id = 1)); @@ -76,7 +76,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file'); insert into user_info (user_id, login, password) -values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', 'password'); +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', '$2a$10$Q/V.mYhHJA.955Lcnd1t1OoqSE6g0ZRi7xQsqnJEUbymIbpl0GvJi'); insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), (select authority.id from authority where id = 1)); From a987949aad46858891ccfc3bff643fb57868d696 Mon Sep 17 00:00:00 2001 From: Maslyna Date: Mon, 27 Mar 2023 19:23:22 +0200 Subject: [PATCH 18/28] BugFix --- .../com/provedcode/config/InitConfig.java | 29 +++++++++++++++++++ .../controllers/AuthenticationController.java | 5 ---- .../user/model/entity/UserInfo.java | 2 +- src/main/resources/data.sql | 4 +-- 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/provedcode/config/InitConfig.java diff --git a/src/main/java/com/provedcode/config/InitConfig.java b/src/main/java/com/provedcode/config/InitConfig.java new file mode 100644 index 0000000..6b0c299 --- /dev/null +++ b/src/main/java/com/provedcode/config/InitConfig.java @@ -0,0 +1,29 @@ +package com.provedcode.config; + +import com.provedcode.user.mapper.UserInfoMapper; +import com.provedcode.user.repo.UserInfoRepository; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@AllArgsConstructor +public class InitConfig implements CommandLineRunner { + UserInfoRepository userInfoRepository; + PasswordEncoder passwordEncoder; + UserInfoMapper userInfoMapper; + + @Override + public void run(String... args) throws Exception { + userInfoRepository.saveAll( + userInfoRepository.findAll().stream() + .map(i -> { + i.setPassword(passwordEncoder.encode(i.getPassword())); + return i; + }).toList()); + log.info("userInfoRepository = {}", userInfoRepository.findAll().stream().map(userInfoMapper::toUserDetails).toList()); + } +} diff --git a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java index 48e1876..68cc240 100644 --- a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java @@ -28,11 +28,6 @@ public class AuthenticationController { UserInfoRepository repository; PasswordEncoder passwordEncoder; - @GetMapping("/test") - List test() { - return repository.findAll().stream().map(i -> i.getPassword() + " : " + passwordEncoder.encode(i.getPassword())).toList(); - } - @PostMapping("/api/login") String login(Authentication authentication) { log.info("=== POST /login === auth.name = {}", authentication.getName()); diff --git a/src/main/java/com/provedcode/user/model/entity/UserInfo.java b/src/main/java/com/provedcode/user/model/entity/UserInfo.java index bdbfe03..d996c62 100644 --- a/src/main/java/com/provedcode/user/model/entity/UserInfo.java +++ b/src/main/java/com/provedcode/user/model/entity/UserInfo.java @@ -36,6 +36,6 @@ public class UserInfo { @OneToOne(orphanRemoval = true) @JoinColumn(name = "user_id", insertable = false, updatable = false) private Talent talent; - @OneToMany(mappedBy = "userInfo", orphanRemoval = true) + @OneToMany(mappedBy = "userInfo", orphanRemoval = true, fetch = FetchType.EAGER) private Set userAuthorities = new LinkedHashSet<>(); } \ No newline at end of file diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index a92ba47..7e1c2fb 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -39,7 +39,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'third_file'); insert into user_info (user_id, login, password) -values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', '$2a$10$1Np2nFzR58WBHCPEBB8vEuePZTtZFA53A4oHRADiviDVkTyt2Olau'); +values ((select id from talent order by id desc limit 1), 'SerhiiSoloviov', 'password'); insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), (select authority.id from authority where id = 1)); @@ -76,7 +76,7 @@ insert into talent_attached_file (talent_id, attached_file) values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev_third_file'); insert into user_info (user_id, login, password) -values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', '$2a$10$Q/V.mYhHJA.955Lcnd1t1OoqSE6g0ZRi7xQsqnJEUbymIbpl0GvJi'); +values ((select id from talent order by id desc limit 1), 'MykhailoOrdyntsev', 'password'); insert into user_authority (user_id, authority_id) values ((select id from user_info order by id desc limit 1), (select authority.id from authority where id = 1)); From 94c13255f127ae43ded0dadf659488c2008a0c7c Mon Sep 17 00:00:00 2001 From: Maslyna Date: Mon, 27 Mar 2023 21:16:38 +0200 Subject: [PATCH 19/28] commit 3 --- .../com/provedcode/config/InitConfig.java | 3 +- .../com/provedcode/config/SecurityConfig.java | 3 +- .../controllers/AuthenticationController.java | 41 +++++++++++++++++-- .../user/model/dto/RegistrationDTO.java | 14 +++++++ .../user/repo/UserAuthorityRepository.java | 10 +++++ .../user/repo/UserInfoRepository.java | 1 + 6 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java create mode 100644 src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java diff --git a/src/main/java/com/provedcode/config/InitConfig.java b/src/main/java/com/provedcode/config/InitConfig.java index 6b0c299..771a754 100644 --- a/src/main/java/com/provedcode/config/InitConfig.java +++ b/src/main/java/com/provedcode/config/InitConfig.java @@ -17,13 +17,12 @@ public class InitConfig implements CommandLineRunner { UserInfoMapper userInfoMapper; @Override - public void run(String... args) throws Exception { + public void run(String... args) throws Exception { // Method to change passwords for already created users from data.sql userInfoRepository.saveAll( userInfoRepository.findAll().stream() .map(i -> { i.setPassword(passwordEncoder.encode(i.getPassword())); return i; }).toList()); - log.info("userInfoRepository = {}", userInfoRepository.findAll().stream().map(userInfoMapper::toUserDetails).toList()); } } diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index f2dc12e..b9311ef 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -42,8 +42,9 @@ public class SecurityConfig { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(c -> c .requestMatchers("/actuator/health").permitAll() // for DevOps - .requestMatchers("/api/login").permitAll() .requestMatchers(antMatcher("/h2/**")).permitAll() + .requestMatchers("/api/login").permitAll() + .requestMatchers(antMatcher("/api/register")).permitAll() .requestMatchers(antMatcher("/api/talents")).permitAll() .anyRequest().authenticated() ); diff --git a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java index 68cc240..ad6f371 100644 --- a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controllers/AuthenticationController.java @@ -1,21 +1,28 @@ package com.provedcode.user.controllers; +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.db.TalentEntityRepository; +import com.provedcode.user.model.dto.RegistrationDTO; import com.provedcode.user.model.entity.UserInfo; +import com.provedcode.user.repo.UserAuthorityRepository; import com.provedcode.user.repo.UserInfoRepository; +import jakarta.validation.Valid; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; import java.time.Instant; -import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static java.time.temporal.ChronoUnit.MINUTES; @@ -23,12 +30,15 @@ @RestController @AllArgsConstructor @Slf4j +@RequestMapping("/api") public class AuthenticationController { JwtEncoder jwtEncoder; - UserInfoRepository repository; + UserInfoRepository userInfoRepository; + TalentEntityRepository talentEntityRepository; + UserAuthorityRepository userAuthorityRepository; PasswordEncoder passwordEncoder; - @PostMapping("/api/login") + @PostMapping("/login") String login(Authentication authentication) { log.info("=== POST /login === auth.name = {}", authentication.getName()); log.info("=== POST /login === auth = {}", authentication); @@ -43,6 +53,29 @@ String login(Authentication authentication) { return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } + @PostMapping("/register") + String register(@Valid RegistrationDTO user) { + if (userInfoRepository.existsByLogin(user.login())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, String.format("user with login = {%s} already exists", user.login())); + } + Talent talent = talentEntityRepository.save(Talent.builder() + .firstName(user.firstName()) + .lastName(user.lastName()) + .specialization(user.specialization()) + .build()); + userInfoRepository.save( + UserInfo.builder() + .talent(talent) + .userId(talent.getId()) + .userAuthorities(Set.of(userAuthorityRepository.findByAuthority_Authority("ROLE_USER") + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "user didn't created")))) + .login(user.login()) + .password(passwordEncoder.encode(user.password())) + .build() + ); + return "YAY"; + } + private String createScope(Authentication authentication) { return authentication.getAuthorities().stream() diff --git a/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java new file mode 100644 index 0000000..3f2ef02 --- /dev/null +++ b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java @@ -0,0 +1,14 @@ +package com.provedcode.user.model.dto; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Builder; + +@Builder +public record RegistrationDTO( + @NotEmpty String login, + @NotEmpty String password, + @NotEmpty String firstName, + @NotEmpty String lastName, + @NotEmpty String specialization +) { +} diff --git a/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java new file mode 100644 index 0000000..bfe200b --- /dev/null +++ b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java @@ -0,0 +1,10 @@ +package com.provedcode.user.repo; + +import com.provedcode.user.model.entity.UserAuthority; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserAuthorityRepository extends JpaRepository { + Optional findByAuthority_Authority(String authority); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java index 1e78951..50f93ee 100644 --- a/src/main/java/com/provedcode/user/repo/UserInfoRepository.java +++ b/src/main/java/com/provedcode/user/repo/UserInfoRepository.java @@ -6,6 +6,7 @@ import java.util.Optional; public interface UserInfoRepository extends JpaRepository { + boolean existsByLogin(String login); Optional findByLogin(String login); } \ No newline at end of file From 51775bd37f772fda273e3f1a8b21fc9979c0964a Mon Sep 17 00:00:00 2001 From: Ren Date: Mon, 27 Mar 2023 22:48:43 +0300 Subject: [PATCH 20/28] Edit UserAuthorityRepository, AuthenticationController, SecurityConfig and TalentServiceImpl --- .../com/provedcode/config/SecurityConfig.java | 48 +++++++++---------- .../service/impl/TalentServiceImpl.java | 4 ++ .../AuthenticationController.java | 42 ++++++++-------- .../user/repo/UserAuthorityRepository.java | 3 +- 4 files changed, 53 insertions(+), 44 deletions(-) rename src/main/java/com/provedcode/user/{controllers => controller}/AuthenticationController.java (60%) diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index b9311ef..7b7b399 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -55,24 +55,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http.sessionManagement().sessionCreationPolicy(STATELESS); http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) - .exceptionHandling(c -> c - .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) - .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) - ); + .exceptionHandling(c -> c + .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) + .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) + ); return http.build(); } - @Bean - UserDetailsService userDetailsService ( - UserInfoRepository repository, - UserInfoMapper mapper - ) { - return login -> repository.findByLogin(login) - .map(mapper::toUserDetails) - .orElseThrow(() -> new UsernameNotFoundException(login + " not found")); - } - @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -85,6 +75,26 @@ public KeyPair keyPair() throws NoSuchAlgorithmException { return keyPairGenerator.generateKeyPair(); } + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + grantedAuthoritiesConverter.setAuthorityPrefix(""); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } + + @Bean + UserDetailsService userDetailsService( + UserInfoRepository repository, + UserInfoMapper mapper + ) { + return login -> repository.findByLogin(login) + .map(mapper::toUserDetails) + .orElseThrow(() -> new UsernameNotFoundException(login + " not found")); + } + @Bean JwtDecoder jwtDecoder(KeyPair keyPair) { return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build(); @@ -96,14 +106,4 @@ JwtEncoder jwtEncoder(KeyPair keyPair) { var jwkSet = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwkSet); } - - @Bean - public JwtAuthenticationConverter jwtAuthenticationConverter() { - JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); - grantedAuthoritiesConverter.setAuthorityPrefix(""); - - JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); - jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); - return jwtAuthenticationConverter; - } } diff --git a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java index 8512d29..eb1217d 100644 --- a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -12,6 +12,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; import java.util.Optional; @@ -22,12 +23,14 @@ @Service @Slf4j @AllArgsConstructor +@Transactional public class TalentServiceImpl implements TalentService { TalentMapper talentMapper; TalentRepository talentRepository; PageProperties pageProperties; @Override + @Transactional(readOnly = true) public Page getTalentsPage(Optional page, Optional size) { if (page.orElse(pageProperties.defaultPageNum()) < 0) { throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); @@ -42,6 +45,7 @@ public Page getTalentsPage(Optional page, Optional talent = talentRepository.findById(id); if (talent.isEmpty()) { diff --git a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java similarity index 60% rename from src/main/java/com/provedcode/user/controllers/AuthenticationController.java rename to src/main/java/com/provedcode/user/controller/AuthenticationController.java index ad6f371..ae24807 100644 --- a/src/main/java/com/provedcode/user/controllers/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -1,8 +1,9 @@ -package com.provedcode.user.controllers; +package com.provedcode.user.controller; import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.db.TalentEntityRepository; import com.provedcode.user.model.dto.RegistrationDTO; +import com.provedcode.user.model.entity.UserAuthority; import com.provedcode.user.model.entity.UserInfo; import com.provedcode.user.repo.UserAuthorityRepository; import com.provedcode.user.repo.UserInfoRepository; @@ -17,6 +18,7 @@ import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; @@ -44,31 +46,34 @@ String login(Authentication authentication) { log.info("=== POST /login === auth = {}", authentication); var now = Instant.now(); var claims = JwtClaimsSet.builder() - .issuer("self") - .issuedAt(now) - .expiresAt(now.plus(5, MINUTES)) - .subject(authentication.getName()) - .claim("scope", createScope(authentication)) - .build(); + .issuer("self") + .issuedAt(now) + .expiresAt(now.plus(5, MINUTES)) + .subject(authentication.getName()) + .claim("scope", createScope(authentication)) + .build(); return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } @PostMapping("/register") - String register(@Valid RegistrationDTO user) { + String register(@RequestBody @Valid RegistrationDTO user) { if (userInfoRepository.existsByLogin(user.login())) { - throw new ResponseStatusException(HttpStatus.CONFLICT, String.format("user with login = {%s} already exists", user.login())); + throw new ResponseStatusException(HttpStatus.CONFLICT, + String.format("user with login = {%s} already exists", user.login())); } Talent talent = talentEntityRepository.save(Talent.builder() - .firstName(user.firstName()) - .lastName(user.lastName()) - .specialization(user.specialization()) - .build()); + .firstName(user.firstName()) + .lastName(user.lastName()) + .specialization(user.specialization()) + .build()); userInfoRepository.save( UserInfo.builder() - .talent(talent) .userId(talent.getId()) - .userAuthorities(Set.of(userAuthorityRepository.findByAuthority_Authority("ROLE_USER") - .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "user didn't created")))) +// .userAuthorities(Set.of(userAuthorityRepository.findById(Integer.toUnsignedLong(1)) +// .orElseThrow(() -> new ResponseStatusException( +// HttpStatus.BAD_REQUEST, +// "user didn't created")))) + .userAuthorities(userAuthorityRepository.findByAuthority_Authority("ROLE_TALENT")) .login(user.login()) .password(passwordEncoder.encode(user.password())) .build() @@ -76,10 +81,9 @@ String register(@Valid RegistrationDTO user) { return "YAY"; } - private String createScope(Authentication authentication) { return authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(" ")); + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(" ")); } } diff --git a/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java index bfe200b..b65c5a4 100644 --- a/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java +++ b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java @@ -4,7 +4,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; +import java.util.Set; public interface UserAuthorityRepository extends JpaRepository { - Optional findByAuthority_Authority(String authority); + Set findByAuthority_Authority(String authority); } \ No newline at end of file From 11b01ccbf3a41465c6dd0e8b2e09c0f10aab180f Mon Sep 17 00:00:00 2001 From: Ren Date: Tue, 28 Mar 2023 02:43:17 +0300 Subject: [PATCH 21/28] Create trash in AuthenticationController --- .../controller/AuthenticationController.java | 40 +++++++++++++------ .../user/model/dto/RegistrationDTO.java | 9 ++++- .../user/model/entity/UserAuthority.java | 6 ++- .../user/repo/AuthorityRepository.java | 7 ++++ 4 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/provedcode/user/repo/AuthorityRepository.java diff --git a/src/main/java/com/provedcode/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java index ae24807..02589a1 100644 --- a/src/main/java/com/provedcode/user/controller/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -5,6 +5,7 @@ import com.provedcode.user.model.dto.RegistrationDTO; import com.provedcode.user.model.entity.UserAuthority; import com.provedcode.user.model.entity.UserInfo; +import com.provedcode.user.repo.AuthorityRepository; import com.provedcode.user.repo.UserAuthorityRepository; import com.provedcode.user.repo.UserInfoRepository; import jakarta.validation.Valid; @@ -38,6 +39,7 @@ public class AuthenticationController { UserInfoRepository userInfoRepository; TalentEntityRepository talentEntityRepository; UserAuthorityRepository userAuthorityRepository; + AuthorityRepository authorityRepository; PasswordEncoder passwordEncoder; @PostMapping("/login") @@ -66,18 +68,32 @@ String register(@RequestBody @Valid RegistrationDTO user) { .lastName(user.lastName()) .specialization(user.specialization()) .build()); - userInfoRepository.save( - UserInfo.builder() - .userId(talent.getId()) -// .userAuthorities(Set.of(userAuthorityRepository.findById(Integer.toUnsignedLong(1)) -// .orElseThrow(() -> new ResponseStatusException( -// HttpStatus.BAD_REQUEST, -// "user didn't created")))) - .userAuthorities(userAuthorityRepository.findByAuthority_Authority("ROLE_TALENT")) - .login(user.login()) - .password(passwordEncoder.encode(user.password())) - .build() - ); + + UserInfo userInfo = UserInfo.builder() + .userId(talent.getId()) + .login(user.login()) + .password(passwordEncoder.encode(user.password())) + .build(); + UserAuthority userAuthority = UserAuthority.builder().userInfo(userInfo) + .authority(authorityRepository.findById(1L).orElseThrow( + () -> new ResponseStatusException( + HttpStatus.BAD_REQUEST, + "user didn't created"))).build(); + userInfo.setUserAuthorities(Set.of(userAuthority)); + userAuthority.setUserInfo(userInfoRepository.save(userInfo)); + userAuthorityRepository.save(userAuthority); +// userInfoRepository.save( +// UserInfo.builder() +// .userId(talent.getId()) +//// .userAuthorities(Set.of(userAuthorityRepository.findById(Integer.toUnsignedLong(1)) +//// .orElseThrow(() -> new ResponseStatusException( +//// HttpStatus.BAD_REQUEST, +//// "user didn't created")))) +// .userAuthorities(userAuthorityRepository.findByAuthority_Authority("ROLE_TALENT")) +// .login(user.login()) +// .password(passwordEncoder.encode(user.password())) +// .build() +// ); return "YAY"; } diff --git a/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java index 3f2ef02..ae3ae5f 100644 --- a/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java +++ b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java @@ -1,5 +1,6 @@ package com.provedcode.user.model.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotEmpty; import lombok.Builder; @@ -7,8 +8,12 @@ public record RegistrationDTO( @NotEmpty String login, @NotEmpty String password, - @NotEmpty String firstName, - @NotEmpty String lastName, + @JsonProperty("first_name") + @NotEmpty + String firstName, + @JsonProperty("last_name") + @NotEmpty + String lastName, @NotEmpty String specialization ) { } diff --git a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java index 5a71b00..64019a5 100644 --- a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java +++ b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java @@ -1,12 +1,14 @@ package com.provedcode.user.model.entity; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import java.util.LinkedHashSet; import java.util.Set; +@Builder +@AllArgsConstructor +@NoArgsConstructor @Getter @Setter @Entity diff --git a/src/main/java/com/provedcode/user/repo/AuthorityRepository.java b/src/main/java/com/provedcode/user/repo/AuthorityRepository.java new file mode 100644 index 0000000..dc3569d --- /dev/null +++ b/src/main/java/com/provedcode/user/repo/AuthorityRepository.java @@ -0,0 +1,7 @@ +package com.provedcode.user.repo; + +import com.provedcode.user.model.entity.Authority; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthorityRepository extends JpaRepository { +} \ No newline at end of file From e40af7f1f52392cc33eb45039a5c863e1e2d772c Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 28 Mar 2023 02:47:48 +0200 Subject: [PATCH 22/28] Code refactor BugFix: nullPointerExceptions in TalentMapperImpl were removed --- .../talent/mapper/impl/TalentMapperImpl.java | 4 +- .../talent/model/dto/FullTalentDTO.java | 4 +- .../talent/model/dto/ShortTalentDTO.java | 4 +- .../controller/AuthenticationController.java | 87 ++--------------- .../user/service/AuthenticationService.java | 9 ++ .../impl/AuthenticationServiceImpl.java | 96 +++++++++++++++++++ 6 files changed, 117 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/provedcode/user/service/AuthenticationService.java create mode 100644 src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java diff --git a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java index 90516ac..596e810 100644 --- a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java +++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java @@ -25,8 +25,8 @@ public FullTalentDTO talentToFullTalentDTO(Talent talent) { .id(talent.getId()) .firstname(talent.getFirstName()) .lastname(talent.getLastName()) - .bio(talent.getTalentDescription().getBio()) - .additionalInfo(talent.getTalentDescription().getAdditionalInfo()) + .bio(talent.getTalentDescription() != null ? talent.getTalentDescription().getBio() : null) + .additionalInfo(talent.getTalentDescription() != null ? talent.getTalentDescription().getAdditionalInfo() : null) .image(talent.getImage()) .specialization(talent.getSpecialization()) .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) diff --git a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java index a8a72c2..4bd9394 100644 --- a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java +++ b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java @@ -7,8 +7,8 @@ @Builder public record FullTalentDTO ( Long id, - String firstname, - String lastname, + String firstName, + String lastName, String image, String specialization, String additionalInfo, diff --git a/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java index a9ca9a7..491bcdb 100644 --- a/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java +++ b/src/main/java/com/provedcode/talent/model/dto/ShortTalentDTO.java @@ -8,8 +8,8 @@ public record ShortTalentDTO( Long id, String image, - String firstname, - String lastname, + String firstName, + String lastName, String specialization, List skills ) { diff --git a/src/main/java/com/provedcode/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java index 02589a1..a0b5b3e 100644 --- a/src/main/java/com/provedcode/user/controller/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -1,105 +1,30 @@ package com.provedcode.user.controller; -import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.repo.db.TalentEntityRepository; import com.provedcode.user.model.dto.RegistrationDTO; -import com.provedcode.user.model.entity.UserAuthority; -import com.provedcode.user.model.entity.UserInfo; -import com.provedcode.user.repo.AuthorityRepository; -import com.provedcode.user.repo.UserAuthorityRepository; -import com.provedcode.user.repo.UserInfoRepository; +import com.provedcode.user.service.AuthenticationService; import jakarta.validation.Valid; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.time.Instant; -import java.util.Set; -import java.util.stream.Collectors; - -import static java.time.temporal.ChronoUnit.MINUTES; +import org.springframework.web.bind.annotation.*; @RestController @AllArgsConstructor @Slf4j @RequestMapping("/api") public class AuthenticationController { - JwtEncoder jwtEncoder; - UserInfoRepository userInfoRepository; - TalentEntityRepository talentEntityRepository; - UserAuthorityRepository userAuthorityRepository; - AuthorityRepository authorityRepository; - PasswordEncoder passwordEncoder; + AuthenticationService authenticationService; @PostMapping("/login") String login(Authentication authentication) { - log.info("=== POST /login === auth.name = {}", authentication.getName()); - log.info("=== POST /login === auth = {}", authentication); - var now = Instant.now(); - var claims = JwtClaimsSet.builder() - .issuer("self") - .issuedAt(now) - .expiresAt(now.plus(5, MINUTES)) - .subject(authentication.getName()) - .claim("scope", createScope(authentication)) - .build(); - return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + return authenticationService.login(authentication); } @PostMapping("/register") + @ResponseStatus(HttpStatus.OK) String register(@RequestBody @Valid RegistrationDTO user) { - if (userInfoRepository.existsByLogin(user.login())) { - throw new ResponseStatusException(HttpStatus.CONFLICT, - String.format("user with login = {%s} already exists", user.login())); - } - Talent talent = talentEntityRepository.save(Talent.builder() - .firstName(user.firstName()) - .lastName(user.lastName()) - .specialization(user.specialization()) - .build()); - - UserInfo userInfo = UserInfo.builder() - .userId(talent.getId()) - .login(user.login()) - .password(passwordEncoder.encode(user.password())) - .build(); - UserAuthority userAuthority = UserAuthority.builder().userInfo(userInfo) - .authority(authorityRepository.findById(1L).orElseThrow( - () -> new ResponseStatusException( - HttpStatus.BAD_REQUEST, - "user didn't created"))).build(); - userInfo.setUserAuthorities(Set.of(userAuthority)); - userAuthority.setUserInfo(userInfoRepository.save(userInfo)); - userAuthorityRepository.save(userAuthority); -// userInfoRepository.save( -// UserInfo.builder() -// .userId(talent.getId()) -//// .userAuthorities(Set.of(userAuthorityRepository.findById(Integer.toUnsignedLong(1)) -//// .orElseThrow(() -> new ResponseStatusException( -//// HttpStatus.BAD_REQUEST, -//// "user didn't created")))) -// .userAuthorities(userAuthorityRepository.findByAuthority_Authority("ROLE_TALENT")) -// .login(user.login()) -// .password(passwordEncoder.encode(user.password())) -// .build() -// ); - return "YAY"; + return authenticationService.register(user); } - private String createScope(Authentication authentication) { - return authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(" ")); - } } diff --git a/src/main/java/com/provedcode/user/service/AuthenticationService.java b/src/main/java/com/provedcode/user/service/AuthenticationService.java new file mode 100644 index 0000000..2bce3b4 --- /dev/null +++ b/src/main/java/com/provedcode/user/service/AuthenticationService.java @@ -0,0 +1,9 @@ +package com.provedcode.user.service; + +import com.provedcode.user.model.dto.RegistrationDTO; +import org.springframework.security.core.Authentication; + +public interface AuthenticationService { + String login(Authentication authentication); + String register(RegistrationDTO user); +} diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java new file mode 100644 index 0000000..606f72e --- /dev/null +++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java @@ -0,0 +1,96 @@ +package com.provedcode.user.service.impl; + +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.repo.db.TalentEntityRepository; +import com.provedcode.user.model.dto.RegistrationDTO; +import com.provedcode.user.model.entity.UserAuthority; +import com.provedcode.user.model.entity.UserInfo; +import com.provedcode.user.repo.AuthorityRepository; +import com.provedcode.user.repo.UserAuthorityRepository; +import com.provedcode.user.repo.UserInfoRepository; +import com.provedcode.user.service.AuthenticationService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.time.Instant; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.time.temporal.ChronoUnit.MINUTES; + +@Service +@AllArgsConstructor +@Slf4j +public class AuthenticationServiceImpl implements AuthenticationService { + JwtEncoder jwtEncoder; + UserInfoRepository userInfoRepository; + TalentEntityRepository talentEntityRepository; + UserAuthorityRepository userAuthorityRepository; + AuthorityRepository authorityRepository; + PasswordEncoder passwordEncoder; + + @Transactional + public String login(Authentication authentication) { + log.info("=== POST /login === auth.name = {}", authentication.getName()); + log.info("=== POST /login === auth = {}", authentication); + var now = Instant.now(); + var claims = JwtClaimsSet.builder() + .issuer("self") + .issuedAt(now) + .expiresAt(now.plus(5, MINUTES)) + .subject(authentication.getName()) + .claim("scope", createScope(authentication)) + .build(); + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + } + + @Transactional + public String register(RegistrationDTO user) { + if (userInfoRepository.existsByLogin(user.login())) { + throw new ResponseStatusException(HttpStatus.CONFLICT, + String.format("user with login = {%s} already exists", user.login())); + } + Talent talent = talentEntityRepository.save( + Talent.builder() + .firstName(user.firstName()) + .lastName(user.lastName()) + .specialization(user.specialization()) + .build() + ); + + UserInfo userInfo = UserInfo.builder() + .userId(talent.getId()) + .login(user.login()) + .password(passwordEncoder.encode(user.password())) + .build(); + + UserAuthority userAuthority = UserAuthority.builder() + .userInfo(userInfo) + .authority(authorityRepository.findById(1L) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "user didn't created"))) + .build(); + + userInfo.setUserAuthorities(Set.of(userAuthority)); + userAuthority.setUserInfo(userInfoRepository.save(userInfo)); + + userAuthorityRepository.save(userAuthority); + + return "User was saved"; + } + + private String createScope(Authentication authentication) { + return authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(" ")); + } +} From e007bc8ab2e7c09f528c3c35de5c09293a36b835 Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 28 Mar 2023 02:56:09 +0200 Subject: [PATCH 23/28] BugFix: don`t ask me --- .../provedcode/talent/mapper/impl/TalentMapperImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java index 596e810..683bf88 100644 --- a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java +++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java @@ -13,8 +13,8 @@ public ShortTalentDTO talentToShortTalentDTO(Talent talent) { return ShortTalentDTO.builder() .id(talent.getId()) .image(talent.getImage()) - .firstname(talent.getFirstName()) - .lastname(talent.getLastName()) + .firstName(talent.getFirstName()) + .lastName(talent.getLastName()) .specialization(talent.getSpecialization()) .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) .build(); @@ -23,8 +23,8 @@ public ShortTalentDTO talentToShortTalentDTO(Talent talent) { public FullTalentDTO talentToFullTalentDTO(Talent talent) { return FullTalentDTO.builder() .id(talent.getId()) - .firstname(talent.getFirstName()) - .lastname(talent.getLastName()) + .firstName(talent.getFirstName()) + .lastName(talent.getLastName()) .bio(talent.getTalentDescription() != null ? talent.getTalentDescription().getBio() : null) .additionalInfo(talent.getTalentDescription() != null ? talent.getTalentDescription().getAdditionalInfo() : null) .image(talent.getImage()) From 83c8ee95efe433df4092fb59f7fcf15ae9c637d3 Mon Sep 17 00:00:00 2001 From: Ren Date: Tue, 28 Mar 2023 17:13:07 +0300 Subject: [PATCH 24/28] Add enum Role, add findByAuthority to AuthorityRepository, do some refactor --- .../handlers/TalentExceptionHandler.java | 2 +- .../talent/mapper/impl/TalentMapperImpl.java | 42 ++++++++++--------- .../talent/model/entity/Talent.java | 1 - .../user/mapper/UserInfoMapperImpl.java | 23 ---------- .../user/mapper/impl/UserInfoMapperImpl.java | 24 +++++++++++ .../java/com/provedcode/user/model/Role.java | 15 +++++++ .../user/model/entity/Authority.java | 2 +- .../user/model/entity/UserAuthority.java | 6 +-- .../user/model/entity/UserInfo.java | 1 - .../user/repo/AuthorityRepository.java | 3 ++ .../user/repo/UserAuthorityRepository.java | 1 - .../impl/AuthenticationServiceImpl.java | 5 ++- src/main/resources/schema.sql | 6 +-- 13 files changed, 76 insertions(+), 55 deletions(-) delete mode 100644 src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java create mode 100644 src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java create mode 100644 src/main/java/com/provedcode/user/model/Role.java diff --git a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java index 86300b8..575e4a5 100644 --- a/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java +++ b/src/main/java/com/provedcode/handlers/TalentExceptionHandler.java @@ -8,7 +8,7 @@ @ControllerAdvice public class TalentExceptionHandler { @ExceptionHandler(ResponseStatusException.class) - private ResponseEntity responseStatusExceptionHandler(ResponseStatusException exception) { + private ResponseEntity responseStatusExceptionHandler(ResponseStatusException exception) { return ResponseEntity.status(exception.getStatusCode()).body(exception.getBody()); } } diff --git a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java index 683bf88..2b0794b 100644 --- a/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java +++ b/src/main/java/com/provedcode/talent/mapper/impl/TalentMapperImpl.java @@ -11,28 +11,32 @@ public class TalentMapperImpl implements TalentMapper { @Override public ShortTalentDTO talentToShortTalentDTO(Talent talent) { return ShortTalentDTO.builder() - .id(talent.getId()) - .image(talent.getImage()) - .firstName(talent.getFirstName()) - .lastName(talent.getLastName()) - .specialization(talent.getSpecialization()) - .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) - .build(); + .id(talent.getId()) + .image(talent.getImage()) + .firstName(talent.getFirstName()) + .lastName(talent.getLastName()) + .specialization(talent.getSpecialization()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .build(); } + @Override public FullTalentDTO talentToFullTalentDTO(Talent talent) { return FullTalentDTO.builder() - .id(talent.getId()) - .firstName(talent.getFirstName()) - .lastName(talent.getLastName()) - .bio(talent.getTalentDescription() != null ? talent.getTalentDescription().getBio() : null) - .additionalInfo(talent.getTalentDescription() != null ? talent.getTalentDescription().getAdditionalInfo() : null) - .image(talent.getImage()) - .specialization(talent.getSpecialization()) - .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) - .contacts(talent.getTalentContacts().stream().map(TalentContact::getContact).toList()) - .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) - .attachedFiles(talent.getTalentAttachedFiles().stream().map(TalentAttachedFile::getAttachedFile).toList()) - .build(); + .id(talent.getId()) + .firstName(talent.getFirstName()) + .lastName(talent.getLastName()) + .bio(talent.getTalentDescription() != null ? talent.getTalentDescription().getBio() : null) + .additionalInfo(talent.getTalentDescription() != null ? talent.getTalentDescription() + .getAdditionalInfo() : null) + .image(talent.getImage()) + .specialization(talent.getSpecialization()) + .links(talent.getTalentLinks().stream().map(TalentLink::getLink).toList()) + .contacts(talent.getTalentContacts().stream().map(TalentContact::getContact).toList()) + .skills(talent.getTalentSkills().stream().map(TalentSkill::getSkill).toList()) + .attachedFiles( + talent.getTalentAttachedFiles().stream().map(TalentAttachedFile::getAttachedFile) + .toList()) + .build(); } } diff --git a/src/main/java/com/provedcode/talent/model/entity/Talent.java b/src/main/java/com/provedcode/talent/model/entity/Talent.java index f52f6f1..232de47 100644 --- a/src/main/java/com/provedcode/talent/model/entity/Talent.java +++ b/src/main/java/com/provedcode/talent/model/entity/Talent.java @@ -46,5 +46,4 @@ public class Talent { private List talentContacts = new ArrayList<>(); @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) private List talentAttachedFiles = new ArrayList<>(); - } \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java b/src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java deleted file mode 100644 index fc2db37..0000000 --- a/src/main/java/com/provedcode/user/mapper/UserInfoMapperImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.provedcode.user.mapper; - -import com.provedcode.user.model.entity.UserInfo; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; - -@Component -public class UserInfoMapperImpl implements UserInfoMapper { - @Override - public UserDetails toUserDetails(UserInfo user) { - return User.withUsername(user.getLogin()) - .password(user.getPassword()) - .authorities(user.getUserAuthorities() - .stream() - .map(i -> new SimpleGrantedAuthority( - i.getAuthority() - .getAuthority())) - .toList()) - .build(); - } -} diff --git a/src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java b/src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java new file mode 100644 index 0000000..9db05e9 --- /dev/null +++ b/src/main/java/com/provedcode/user/mapper/impl/UserInfoMapperImpl.java @@ -0,0 +1,24 @@ +package com.provedcode.user.mapper.impl; + +import com.provedcode.user.mapper.UserInfoMapper; +import com.provedcode.user.model.entity.UserInfo; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +public class UserInfoMapperImpl implements UserInfoMapper { + @Override + public UserDetails toUserDetails(UserInfo user) { + return User.withUsername(user.getLogin()) + .password(user.getPassword()) + .authorities(user.getUserAuthorities() + .stream() + .map(i -> new SimpleGrantedAuthority( + i.getAuthority() + .getAuthority())) + .toList()) + .build(); + } +} diff --git a/src/main/java/com/provedcode/user/model/Role.java b/src/main/java/com/provedcode/user/model/Role.java new file mode 100644 index 0000000..d1f1e35 --- /dev/null +++ b/src/main/java/com/provedcode/user/model/Role.java @@ -0,0 +1,15 @@ +package com.provedcode.user.model; + +public enum Role { + TALENT("ROLE_TALENT"); + private final String userRole; + + Role(String role) { + this.userRole = role; + } + + @Override + public String toString() { + return this.userRole; + } +} diff --git a/src/main/java/com/provedcode/user/model/entity/Authority.java b/src/main/java/com/provedcode/user/model/entity/Authority.java index c722696..dc8a965 100644 --- a/src/main/java/com/provedcode/user/model/entity/Authority.java +++ b/src/main/java/com/provedcode/user/model/entity/Authority.java @@ -17,6 +17,6 @@ public class Authority { private Long id; @NotEmpty @NotNull - @Column(name = "authority", length = 50) + @Column(name = "authority", length = 20) private String authority; } \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java index 64019a5..d8bbcf7 100644 --- a/src/main/java/com/provedcode/user/model/entity/UserAuthority.java +++ b/src/main/java/com/provedcode/user/model/entity/UserAuthority.java @@ -1,11 +1,9 @@ package com.provedcode.user.model.entity; import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; import lombok.*; -import java.util.LinkedHashSet; -import java.util.Set; - @Builder @AllArgsConstructor @NoArgsConstructor @@ -18,9 +16,11 @@ public class UserAuthority { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; + @NotNull @ManyToOne @JoinColumn(name = "user_id") private UserInfo userInfo; + @NotNull @ManyToOne @JoinColumn(name = "authority_id") private Authority authority; diff --git a/src/main/java/com/provedcode/user/model/entity/UserInfo.java b/src/main/java/com/provedcode/user/model/entity/UserInfo.java index d996c62..2f7efca 100644 --- a/src/main/java/com/provedcode/user/model/entity/UserInfo.java +++ b/src/main/java/com/provedcode/user/model/entity/UserInfo.java @@ -2,7 +2,6 @@ import com.provedcode.talent.model.entity.Talent; import jakarta.persistence.*; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.*; diff --git a/src/main/java/com/provedcode/user/repo/AuthorityRepository.java b/src/main/java/com/provedcode/user/repo/AuthorityRepository.java index dc3569d..dbb713e 100644 --- a/src/main/java/com/provedcode/user/repo/AuthorityRepository.java +++ b/src/main/java/com/provedcode/user/repo/AuthorityRepository.java @@ -3,5 +3,8 @@ import com.provedcode.user.model.entity.Authority; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface AuthorityRepository extends JpaRepository { + Optional findByAuthority(String authority); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java index b65c5a4..ef91bfc 100644 --- a/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java +++ b/src/main/java/com/provedcode/user/repo/UserAuthorityRepository.java @@ -7,5 +7,4 @@ import java.util.Set; public interface UserAuthorityRepository extends JpaRepository { - Set findByAuthority_Authority(String authority); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java index 606f72e..4356e21 100644 --- a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java @@ -2,6 +2,7 @@ import com.provedcode.talent.model.entity.Talent; import com.provedcode.talent.repo.db.TalentEntityRepository; +import com.provedcode.user.model.Role; import com.provedcode.user.model.dto.RegistrationDTO; import com.provedcode.user.model.entity.UserAuthority; import com.provedcode.user.model.entity.UserInfo; @@ -76,8 +77,8 @@ public String register(RegistrationDTO user) { UserAuthority userAuthority = UserAuthority.builder() .userInfo(userInfo) - .authority(authorityRepository.findById(1L) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "user didn't created"))) + .authority(authorityRepository.findByAuthority(Role.TALENT.toString()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "this authority does`t exist"))) .build(); userInfo.setUserAuthorities(Set.of(userAuthority)); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 42570c3..2166cfd 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -64,7 +64,7 @@ create TABLE talent_attached_file ( alter table talent_attached_file add CONSTRAINT FK_TALENT_ATTACHED_FILE_ON_TALENT FOREIGN KEY (talent_id) REFERENCES talent (id); --user tables-- -CREATE TABLE user_info ( +create TABLE user_info ( id BIGINT AUTO_INCREMENT NOT NULL, user_id BIGINT NOT NULL, login VARCHAR(100) NOT NULL, @@ -72,11 +72,11 @@ CREATE TABLE user_info ( CONSTRAINT pk_user_info PRIMARY KEY (id) ); -ALTER TABLE user_info ADD CONSTRAINT FK_USER_INFO_ON_USER FOREIGN KEY (user_id) REFERENCES talent (id); +alter table user_info add CONSTRAINT FK_USER_INFO_ON_USER FOREIGN KEY (user_id) REFERENCES talent (id); create TABLE authority ( id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, - authority VARCHAR(50) NOT NULL, + authority VARCHAR(20) NOT NULL, CONSTRAINT pk_authority PRIMARY KEY (id) ); From 762e05dfa925573365eca5b3397c1aa341e1994c Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 28 Mar 2023 20:21:12 +0200 Subject: [PATCH 25/28] Refactored code User registration returns jwt-token --- .../com/provedcode/config/InitConfig.java | 3 +- .../com/provedcode/config/SecurityConfig.java | 4 +-- .../controller/AuthenticationController.java | 5 ++-- .../user/service/AuthenticationService.java | 5 +++- .../impl/AuthenticationServiceImpl.java | 29 +++++++++---------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/provedcode/config/InitConfig.java b/src/main/java/com/provedcode/config/InitConfig.java index 771a754..fe066d2 100644 --- a/src/main/java/com/provedcode/config/InitConfig.java +++ b/src/main/java/com/provedcode/config/InitConfig.java @@ -17,7 +17,8 @@ public class InitConfig implements CommandLineRunner { UserInfoMapper userInfoMapper; @Override - public void run(String... args) throws Exception { // Method to change passwords for already created users from data.sql + public void run(String... args) throws Exception { + // TODO: Method to change passwords for already created users from data.sql userInfoRepository.saveAll( userInfoRepository.findAll().stream() .map(i -> { diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index 7b7b399..3a0596b 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -43,9 +43,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http.authorizeHttpRequests(c -> c .requestMatchers("/actuator/health").permitAll() // for DevOps .requestMatchers(antMatcher("/h2/**")).permitAll() - .requestMatchers("/api/login").permitAll() - .requestMatchers(antMatcher("/api/register")).permitAll() - .requestMatchers(antMatcher("/api/talents")).permitAll() + .requestMatchers(antMatcher("/api/talents/*")).permitAll() .anyRequest().authenticated() ); diff --git a/src/main/java/com/provedcode/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java index a0b5b3e..927e05a 100644 --- a/src/main/java/com/provedcode/user/controller/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -12,13 +12,14 @@ @RestController @AllArgsConstructor @Slf4j -@RequestMapping("/api") +@RequestMapping("/api/talents") +@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE}) public class AuthenticationController { AuthenticationService authenticationService; @PostMapping("/login") String login(Authentication authentication) { - return authenticationService.login(authentication); + return authenticationService.login(authentication.getName(), authentication.getAuthorities()); } @PostMapping("/register") diff --git a/src/main/java/com/provedcode/user/service/AuthenticationService.java b/src/main/java/com/provedcode/user/service/AuthenticationService.java index 2bce3b4..3a7a030 100644 --- a/src/main/java/com/provedcode/user/service/AuthenticationService.java +++ b/src/main/java/com/provedcode/user/service/AuthenticationService.java @@ -2,8 +2,11 @@ import com.provedcode.user.model.dto.RegistrationDTO; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; public interface AuthenticationService { - String login(Authentication authentication); + String login(String name, Collection authorities); String register(RegistrationDTO user); } diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java index 4356e21..4f1f22b 100644 --- a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java @@ -13,8 +13,8 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtEncoder; @@ -24,6 +24,7 @@ import org.springframework.web.server.ResponseStatusException; import java.time.Instant; +import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; @@ -41,16 +42,16 @@ public class AuthenticationServiceImpl implements AuthenticationService { PasswordEncoder passwordEncoder; @Transactional - public String login(Authentication authentication) { - log.info("=== POST /login === auth.name = {}", authentication.getName()); - log.info("=== POST /login === auth = {}", authentication); + public String login(String name, Collection authorities) { + log.info("=== POST /login === auth.name = {}", name); + log.info("=== POST /login === auth = {}", authorities); var now = Instant.now(); var claims = JwtClaimsSet.builder() .issuer("self") .issuedAt(now) .expiresAt(now.plus(5, MINUTES)) - .subject(authentication.getName()) - .claim("scope", createScope(authentication)) + .subject(name) + .claim("scope", authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" "))) .build(); return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } @@ -68,30 +69,26 @@ public String register(RegistrationDTO user) { .specialization(user.specialization()) .build() ); - UserInfo userInfo = UserInfo.builder() .userId(talent.getId()) .login(user.login()) .password(passwordEncoder.encode(user.password())) .build(); - UserAuthority userAuthority = UserAuthority.builder() .userInfo(userInfo) .authority(authorityRepository.findByAuthority(Role.TALENT.toString()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "this authority does`t exist"))) .build(); - userInfo.setUserAuthorities(Set.of(userAuthority)); userAuthority.setUserInfo(userInfoRepository.save(userInfo)); - userAuthorityRepository.save(userAuthority); - return "User was saved"; - } + String userLogin = userInfo.getLogin(); + Collection userAuthorities = userInfo.getUserAuthorities().stream().map(i -> new SimpleGrantedAuthority(i.getAuthority().getAuthority())).toList(); + + log.info("user with login {%s} was saved, his authorities: %s".formatted(userLogin, userAuthorities)); - private String createScope(Authentication authentication) { - return authentication.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(" ")); + return login(userLogin, userAuthorities); } + } From 2b007f50f02ad2c42de66dfe97c3037eb0c210bc Mon Sep 17 00:00:00 2001 From: Maslyna Date: Tue, 28 Mar 2023 20:29:18 +0200 Subject: [PATCH 26/28] Refactored code Created SessionInfoDTO --- .../controller/AuthenticationController.java | 5 +-- .../user/model/dto/SessionInfoDTO.java | 10 ++++++ .../user/service/AuthenticationService.java | 6 ++-- .../impl/AuthenticationServiceImpl.java | 33 +++++++++++-------- 4 files changed, 35 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java diff --git a/src/main/java/com/provedcode/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java index 927e05a..1111d1e 100644 --- a/src/main/java/com/provedcode/user/controller/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -1,6 +1,7 @@ package com.provedcode.user.controller; import com.provedcode.user.model.dto.RegistrationDTO; +import com.provedcode.user.model.dto.SessionInfoDTO; import com.provedcode.user.service.AuthenticationService; import jakarta.validation.Valid; import lombok.AllArgsConstructor; @@ -18,13 +19,13 @@ public class AuthenticationController { AuthenticationService authenticationService; @PostMapping("/login") - String login(Authentication authentication) { + SessionInfoDTO login(Authentication authentication) { return authenticationService.login(authentication.getName(), authentication.getAuthorities()); } @PostMapping("/register") @ResponseStatus(HttpStatus.OK) - String register(@RequestBody @Valid RegistrationDTO user) { + SessionInfoDTO register(@RequestBody @Valid RegistrationDTO user) { return authenticationService.register(user); } diff --git a/src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java b/src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java new file mode 100644 index 0000000..17686d8 --- /dev/null +++ b/src/main/java/com/provedcode/user/model/dto/SessionInfoDTO.java @@ -0,0 +1,10 @@ +package com.provedcode.user.model.dto; + +import lombok.Builder; + +@Builder +public record SessionInfoDTO( + String status, + String token +) { +} diff --git a/src/main/java/com/provedcode/user/service/AuthenticationService.java b/src/main/java/com/provedcode/user/service/AuthenticationService.java index 3a7a030..743015d 100644 --- a/src/main/java/com/provedcode/user/service/AuthenticationService.java +++ b/src/main/java/com/provedcode/user/service/AuthenticationService.java @@ -1,12 +1,12 @@ package com.provedcode.user.service; import com.provedcode.user.model.dto.RegistrationDTO; -import org.springframework.security.core.Authentication; +import com.provedcode.user.model.dto.SessionInfoDTO; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; public interface AuthenticationService { - String login(String name, Collection authorities); - String register(RegistrationDTO user); + SessionInfoDTO login(String name, Collection authorities); + SessionInfoDTO register(RegistrationDTO user); } diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java index 4f1f22b..72fb72f 100644 --- a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java @@ -4,6 +4,7 @@ import com.provedcode.talent.repo.db.TalentEntityRepository; import com.provedcode.user.model.Role; import com.provedcode.user.model.dto.RegistrationDTO; +import com.provedcode.user.model.dto.SessionInfoDTO; import com.provedcode.user.model.entity.UserAuthority; import com.provedcode.user.model.entity.UserInfo; import com.provedcode.user.repo.AuthorityRepository; @@ -42,22 +43,12 @@ public class AuthenticationServiceImpl implements AuthenticationService { PasswordEncoder passwordEncoder; @Transactional - public String login(String name, Collection authorities) { - log.info("=== POST /login === auth.name = {}", name); - log.info("=== POST /login === auth = {}", authorities); - var now = Instant.now(); - var claims = JwtClaimsSet.builder() - .issuer("self") - .issuedAt(now) - .expiresAt(now.plus(5, MINUTES)) - .subject(name) - .claim("scope", authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" "))) - .build(); - return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); + public SessionInfoDTO login(String name, Collection authorities) { + return new SessionInfoDTO("User {%s} log-in".formatted(name), generateJWTToken(name, authorities)); } @Transactional - public String register(RegistrationDTO user) { + public SessionInfoDTO register(RegistrationDTO user) { if (userInfoRepository.existsByLogin(user.login())) { throw new ResponseStatusException(HttpStatus.CONFLICT, String.format("user with login = {%s} already exists", user.login())); @@ -88,7 +79,21 @@ public String register(RegistrationDTO user) { log.info("user with login {%s} was saved, his authorities: %s".formatted(userLogin, userAuthorities)); - return login(userLogin, userAuthorities); + return new SessionInfoDTO("User: {%s} was registered".formatted(userLogin), generateJWTToken(userLogin, userAuthorities)); + } + + private String generateJWTToken(String name, Collection authorities) { + log.info("=== POST /login === auth.name = {}", name); + log.info("=== POST /login === auth = {}", authorities); + var now = Instant.now(); + var claims = JwtClaimsSet.builder() + .issuer("self") + .issuedAt(now) + .expiresAt(now.plus(5, MINUTES)) + .subject(name) + .claim("scope", authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" "))) + .build(); + return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); } } From b5d56c562c25745c50d344df90c69e96eadf64fa Mon Sep 17 00:00:00 2001 From: Maslyna Date: Wed, 29 Mar 2023 15:06:15 +0200 Subject: [PATCH 27/28] Refactored code Added annotation @UrlList --- .../com/provedcode/annotations/UrlList.java | 21 ++++ .../annotations/impl/UrlListValidator.java | 32 +++++ .../com/provedcode/config/SecurityConfig.java | 2 +- .../provedcode/talent/TalentController.java | 20 ++- .../talent/model/dto/FullTalentDTO.java | 41 +++++-- .../talent/model/entity/Talent.java | 23 ++-- .../model/entity/TalentAttachedFile.java | 7 +- .../talent/model/entity/TalentContact.java | 6 +- .../model/entity/TalentDescription.java | 2 + .../talent/model/entity/TalentLink.java | 7 +- .../talent/model/entity/TalentSkill.java | 6 +- .../talent/repo/TalentRepository.java | 20 +-- .../talent/repo/TalentSkillRepository.java | 11 ++ .../repo/db/TalentEntityRepository.java | 20 --- .../talent/service/TalentService.java | 7 +- .../service/impl/TalentServiceImpl.java | 116 +++++++++++++++++- .../service/mock/TalentServiceMock.java | 52 -------- .../controller/AuthenticationController.java | 2 +- .../user/model/dto/RegistrationDTO.java | 3 +- .../impl/AuthenticationServiceImpl.java | 19 +-- src/main/resources/application-dev.properties | 2 + 21 files changed, 290 insertions(+), 129 deletions(-) create mode 100644 src/main/java/com/provedcode/annotations/UrlList.java create mode 100644 src/main/java/com/provedcode/annotations/impl/UrlListValidator.java create mode 100644 src/main/java/com/provedcode/talent/repo/TalentSkillRepository.java delete mode 100644 src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java delete mode 100644 src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java diff --git a/src/main/java/com/provedcode/annotations/UrlList.java b/src/main/java/com/provedcode/annotations/UrlList.java new file mode 100644 index 0000000..6a792bc --- /dev/null +++ b/src/main/java/com/provedcode/annotations/UrlList.java @@ -0,0 +1,21 @@ +package com.provedcode.annotations; + +import com.provedcode.annotations.impl.UrlListValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = UrlListValidator.class) +public @interface UrlList { + String message() default "The list should contain only URLs"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/annotations/impl/UrlListValidator.java b/src/main/java/com/provedcode/annotations/impl/UrlListValidator.java new file mode 100644 index 0000000..ac473d7 --- /dev/null +++ b/src/main/java/com/provedcode/annotations/impl/UrlListValidator.java @@ -0,0 +1,32 @@ +package com.provedcode.annotations.impl; + +import com.provedcode.annotations.UrlList; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.net.URL; +import java.util.List; + +public class UrlListValidator implements ConstraintValidator> { + @Override + public boolean isValid(List value, ConstraintValidatorContext context) { + if (value == null) { + return true; + } + for (String url : value) { + if (!isUrl(url)) { + return false; + } + } + return true; + } + + private boolean isUrl(String url) { + try { + new URL(url).toURI(); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/config/SecurityConfig.java b/src/main/java/com/provedcode/config/SecurityConfig.java index 3a0596b..6591a05 100644 --- a/src/main/java/com/provedcode/config/SecurityConfig.java +++ b/src/main/java/com/provedcode/config/SecurityConfig.java @@ -43,7 +43,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti http.authorizeHttpRequests(c -> c .requestMatchers("/actuator/health").permitAll() // for DevOps .requestMatchers(antMatcher("/h2/**")).permitAll() - .requestMatchers(antMatcher("/api/talents/*")).permitAll() + .requestMatchers(antMatcher("/api/talents/**")).permitAll() .anyRequest().authenticated() ); diff --git a/src/main/java/com/provedcode/talent/TalentController.java b/src/main/java/com/provedcode/talent/TalentController.java index a372bdb..b1e5f8d 100644 --- a/src/main/java/com/provedcode/talent/TalentController.java +++ b/src/main/java/com/provedcode/talent/TalentController.java @@ -3,11 +3,14 @@ import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; import com.provedcode.talent.service.TalentService; +import com.provedcode.user.model.dto.SessionInfoDTO; +import jakarta.validation.Valid; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.Optional; @@ -15,7 +18,8 @@ @Slf4j @RestController @AllArgsConstructor -@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE}) +@CrossOrigin(origins = "*", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, + RequestMethod.DELETE}) @RequestMapping("/api") public class TalentController { TalentService talentService; @@ -33,4 +37,18 @@ Page getTalents(@RequestParam(value = "page") Optional return talentService.getTalentsPage(page, size); } + @PreAuthorize("hasRole('TALENT')") + @PatchMapping("/talents/{talent-id}") + FullTalentDTO editTalent(@PathVariable("talent-id") long id, + @RequestBody @Valid FullTalentDTO fullTalent, + Authentication authentication) { + return talentService.editTalent(id, fullTalent, authentication); + } + + @PreAuthorize("hasRole('TALENT')") + @DeleteMapping("/talents/{id}") + SessionInfoDTO deleteTalent(@PathVariable("id") long id, Authentication authentication) { + return talentService.deleteTalentById(id, authentication); + } + } diff --git a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java index 4bd9394..dc5d09b 100644 --- a/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java +++ b/src/main/java/com/provedcode/talent/model/dto/FullTalentDTO.java @@ -1,20 +1,35 @@ package com.provedcode.talent.model.dto; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.provedcode.annotations.UrlList; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Builder; +import org.hibernate.validator.constraints.URL; import java.util.List; @Builder -public record FullTalentDTO ( - Long id, - String firstName, - String lastName, - String image, - String specialization, - String additionalInfo, - String bio, - List skills, - List links, - List contacts, - List attachedFiles -) {} +public record FullTalentDTO( + Long id, + @NotEmpty + @JsonProperty("first_name") + String firstName, + @NotEmpty + @JsonProperty("last_name") + String lastName, + String image, + @NotEmpty + String specialization, + @JsonProperty("additional_info") + String additionalInfo, + String bio, + List skills, + @UrlList + List links, + List contacts, + @UrlList + @JsonProperty("attached_files") + List attachedFiles +) { +} diff --git a/src/main/java/com/provedcode/talent/model/entity/Talent.java b/src/main/java/com/provedcode/talent/model/entity/Talent.java index 232de47..a8cc0df 100644 --- a/src/main/java/com/provedcode/talent/model/entity/Talent.java +++ b/src/main/java/com/provedcode/talent/model/entity/Talent.java @@ -4,12 +4,14 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.*; +import lombok.experimental.Accessors; import org.hibernate.validator.constraints.URL; import java.util.ArrayList; import java.util.List; @Builder +@Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor @Getter @@ -22,28 +24,33 @@ public class Talent { @Column(nullable = false, insertable = false, updatable = false) private Long id; @NotEmpty - @NotNull @Column(name = "first_name", length = 20) private String firstName; @NotEmpty - @NotNull @Column(name = "last_name", length = 20) private String lastName; @NotEmpty - @NotNull @Column(name = "specialization", length = 30) private String specialization; @URL @Column(name = "image", length = 100) private String image; - @OneToOne(mappedBy = "talent", orphanRemoval = true) + @OneToOne(mappedBy = "talent", cascade = CascadeType.ALL, orphanRemoval = true) private TalentDescription talentDescription; - @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", cascade = CascadeType.ALL, orphanRemoval = true) private List talentLinks = new ArrayList<>(); - @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", cascade = CascadeType.ALL, orphanRemoval = true) private List talentSkills = new ArrayList<>(); - @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", cascade = CascadeType.ALL, orphanRemoval = true) private List talentContacts = new ArrayList<>(); - @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", orphanRemoval = true) + @OneToMany(fetch = FetchType.EAGER, mappedBy = "talent", cascade = CascadeType.ALL, orphanRemoval = true) private List talentAttachedFiles = new ArrayList<>(); + + public void addTalentSkill(TalentSkill talentSkill) { + talentSkills.add(talentSkill); + } + + public void removeTalentSkill(TalentSkill talentSkill) { + talentSkills.remove(talentSkill); + } } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java b/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java index d0eada0..36e2e04 100644 --- a/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java +++ b/src/main/java/com/provedcode/talent/model/entity/TalentAttachedFile.java @@ -2,10 +2,11 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import org.hibernate.validator.constraints.URL; - +@NoArgsConstructor +@AllArgsConstructor +@Builder @Getter @Setter @Entity diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentContact.java b/src/main/java/com/provedcode/talent/model/entity/TalentContact.java index 78bc1e8..83858f4 100644 --- a/src/main/java/com/provedcode/talent/model/entity/TalentContact.java +++ b/src/main/java/com/provedcode/talent/model/entity/TalentContact.java @@ -2,9 +2,11 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; +@NoArgsConstructor +@AllArgsConstructor +@Builder @Getter @Setter @Entity diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java b/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java index ff48a7c..dbf4188 100644 --- a/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java +++ b/src/main/java/com/provedcode/talent/model/entity/TalentDescription.java @@ -3,8 +3,10 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import lombok.*; +import lombok.experimental.Accessors; @Builder +@Accessors(chain = true) @AllArgsConstructor @NoArgsConstructor @Getter diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentLink.java b/src/main/java/com/provedcode/talent/model/entity/TalentLink.java index 8ff7d7c..a567f5e 100644 --- a/src/main/java/com/provedcode/talent/model/entity/TalentLink.java +++ b/src/main/java/com/provedcode/talent/model/entity/TalentLink.java @@ -2,10 +2,11 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; import org.hibernate.validator.constraints.URL; - +@NoArgsConstructor +@AllArgsConstructor +@Builder @Getter @Setter @Entity diff --git a/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java b/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java index 4f312b1..884fb87 100644 --- a/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java +++ b/src/main/java/com/provedcode/talent/model/entity/TalentSkill.java @@ -2,9 +2,11 @@ import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.Setter; +import lombok.*; +@NoArgsConstructor +@AllArgsConstructor +@Builder @Getter @Setter @Entity diff --git a/src/main/java/com/provedcode/talent/repo/TalentRepository.java b/src/main/java/com/provedcode/talent/repo/TalentRepository.java index c077eb8..57723f6 100644 --- a/src/main/java/com/provedcode/talent/repo/TalentRepository.java +++ b/src/main/java/com/provedcode/talent/repo/TalentRepository.java @@ -1,14 +1,16 @@ package com.provedcode.talent.repo; import com.provedcode.talent.model.entity.Talent; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; +import com.provedcode.talent.model.entity.TalentSkill; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - -public interface TalentRepository { - - Page findAll(Pageable pageable); - - Optional findById(Long aLong); +public interface TalentRepository extends + JpaRepository { + @Transactional + @Modifying + @Query("update Talent t set t.talentSkills = ?1") + int updateTalentSkillsBy(TalentSkill talentSkills); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/TalentSkillRepository.java b/src/main/java/com/provedcode/talent/repo/TalentSkillRepository.java new file mode 100644 index 0000000..2c0648e --- /dev/null +++ b/src/main/java/com/provedcode/talent/repo/TalentSkillRepository.java @@ -0,0 +1,11 @@ +package com.provedcode.talent.repo; + +import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.model.entity.TalentSkill; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TalentSkillRepository extends JpaRepository { + List deleteByTalent(Talent talent); +} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java b/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java deleted file mode 100644 index e3fa4a9..0000000 --- a/src/main/java/com/provedcode/talent/repo/db/TalentEntityRepository.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.provedcode.talent.repo.db; - -import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.repo.TalentRepository; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Optional; - -public interface TalentEntityRepository extends - JpaRepository, - TalentRepository { - @Transactional(readOnly = true) - Page findAll(Pageable pageable); - @Override - @Transactional(readOnly = true) - Optional findById(Long aLong); -} \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/service/TalentService.java b/src/main/java/com/provedcode/talent/service/TalentService.java index e583ff2..838f74d 100644 --- a/src/main/java/com/provedcode/talent/service/TalentService.java +++ b/src/main/java/com/provedcode/talent/service/TalentService.java @@ -2,9 +2,10 @@ import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; +import com.provedcode.user.model.dto.SessionInfoDTO; +import org.springframework.security.core.Authentication; import org.springframework.data.domain.Page; -import java.util.List; import java.util.Optional; @@ -12,4 +13,8 @@ public interface TalentService { Page getTalentsPage(Optional page, Optional size); FullTalentDTO getTalentById(long id); + + FullTalentDTO editTalent(long id, FullTalentDTO fullTalent, Authentication authentication); + + SessionInfoDTO deleteTalentById(long id, Authentication authentication); } \ No newline at end of file diff --git a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java index eb1217d..900920f 100644 --- a/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java +++ b/src/main/java/com/provedcode/talent/service/impl/TalentServiceImpl.java @@ -4,30 +4,41 @@ import com.provedcode.talent.mapper.TalentMapper; import com.provedcode.talent.model.dto.FullTalentDTO; import com.provedcode.talent.model.dto.ShortTalentDTO; -import com.provedcode.talent.model.entity.Talent; +import com.provedcode.talent.model.entity.*; import com.provedcode.talent.repo.TalentRepository; +import com.provedcode.talent.repo.TalentSkillRepository; import com.provedcode.talent.service.TalentService; +import com.provedcode.user.model.dto.SessionInfoDTO; +import com.provedcode.user.model.entity.UserInfo; +import com.provedcode.user.repo.AuthorityRepository; +import com.provedcode.user.repo.UserInfoRepository; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.util.List; import java.util.Optional; -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.NOT_FOUND; +import static org.springframework.http.HttpStatus.*; @Service @Slf4j @AllArgsConstructor @Transactional public class TalentServiceImpl implements TalentService { + private final AuthorityRepository authorityRepository; TalentMapper talentMapper; TalentRepository talentRepository; + TalentSkillRepository talentSkillRepository; + UserInfoRepository userInfoRepository; PageProperties pageProperties; + TalentRepository talentEntityRepository; + @Override @Transactional(readOnly = true) @@ -53,4 +64,103 @@ public FullTalentDTO getTalentById(long id) { } return talentMapper.talentToFullTalentDTO(talent.get()); } + + @Override + public FullTalentDTO editTalent(long id, FullTalentDTO fullTalent, Authentication authentication) { + Optional talent = talentRepository.findById(id); + Optional userInfo = userInfoRepository.findByLogin(authentication.getName()); + + if (talent.isEmpty() || userInfo.isEmpty()) { + throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); + } + if (userInfo.get().getTalent().getId() != id) { + throw new ResponseStatusException(FORBIDDEN, "you can`t update another user"); + } + + Talent oldTalent = talent.get(); + long oldTalentId = oldTalent.getId(); + + TalentDescription oldTalentDescription = oldTalent.getTalentDescription(); + if (oldTalentDescription != null) { + oldTalentDescription + .setAdditionalInfo(fullTalent.additionalInfo()) + .setBio(fullTalent.bio()); + } else { + oldTalentDescription = TalentDescription.builder() + .talentId(oldTalentId) + .additionalInfo(fullTalent.additionalInfo()) + .bio(fullTalent.bio()) + .talent(oldTalent) + .build(); + } + + List oldTalentSkills = oldTalent.getTalentSkills(); + oldTalentSkills.clear(); + oldTalentSkills.addAll(fullTalent.skills().stream().map(s -> TalentSkill.builder() + .talentId(oldTalentId) + .talent(oldTalent) + .skill(s).build()).toList()); + + List oldTalentLinks = oldTalent.getTalentLinks(); + oldTalentLinks.clear(); + oldTalentLinks.addAll(fullTalent.links().stream().map(l -> TalentLink.builder() + .talentId(oldTalentId) + .talent(oldTalent) + .link(l).build()).toList()); + + List oldTalentContacts = oldTalent.getTalentContacts(); + oldTalentContacts.clear(); + oldTalentContacts.addAll(fullTalent.contacts().stream().map(s -> TalentContact.builder() + .talentId(oldTalentId) + .talent(oldTalent) + .contact(s).build()).toList()); + List oldTalentAttachedFile = oldTalent.getTalentAttachedFiles(); + oldTalentAttachedFile.clear(); + oldTalentAttachedFile.addAll(fullTalent.attachedFiles().stream().map(s -> TalentAttachedFile.builder() + .talentId( + oldTalentId) + .talent(oldTalent) + .attachedFile(s) + .build()).toList()); + + oldTalent.setFirstName(fullTalent.firstName()) + .setLastName(fullTalent.lastName()) + .setSpecialization(fullTalent.specialization()) + .setImage(fullTalent.image()) + .setTalentDescription(oldTalentDescription) + .setTalentSkills(oldTalentSkills) + .setTalentLinks(oldTalentLinks) + .setTalentContacts(oldTalentContacts) + .setTalentAttachedFiles(oldTalentAttachedFile); + + talentRepository.save(oldTalent); + + return talentMapper.talentToFullTalentDTO(oldTalent); + } + + @Override + public SessionInfoDTO deleteTalentById(long id, Authentication authentication) { + Optional talent = talentRepository.findById(id); + Optional userInfo = userInfoRepository.findByLogin(authentication.getName()); + + if (talent.isEmpty() || userInfo.isEmpty()) { + throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); + } + if (userInfo.get().getTalent().getId() != id) { + throw new ResponseStatusException(FORBIDDEN, "you can`t delete another user"); + } + + userInfoRepository.delete(userInfo.get()); + talentRepository.deleteById(id); + return new SessionInfoDTO("deleted", "null"); + } + +// public void userVerification(Optional talent, Optional userInfo, long id) { +// if (talent.isEmpty()) { +// throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); +// } +// if (userInfo.get().getTalent().getId() != id) { +// throw new ResponseStatusException(UNAUTHORIZED); +// } +// } } diff --git a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java deleted file mode 100644 index b305f3a..0000000 --- a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.provedcode.talent.service.mock; - -import com.provedcode.config.PageProperties; -import com.provedcode.talent.service.TalentService; -import com.provedcode.talent.mapper.TalentMapper; -import com.provedcode.talent.model.dto.FullTalentDTO; -import com.provedcode.talent.model.dto.ShortTalentDTO; -import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.repo.TalentRepository; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.web.server.ResponseStatusException; - -import java.util.Optional; - -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.NOT_FOUND; - -//@Service -@Slf4j -@AllArgsConstructor -public class TalentServiceMock implements TalentService { - TalentMapper talentMapper; - TalentRepository talentRepository; - PageProperties pageProperties; - - @Override - public Page getTalentsPage(Optional page, Optional size) { - if (page.orElse(pageProperties.defaultPageNum()) < 0) { - throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); - } - if (size.orElse(pageProperties.defaultPageSize()) <= 0) { - throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); - } - return new PageImpl<>(talentRepository.findAll( - PageRequest.of(page.orElse(pageProperties.defaultPageNum()), size.orElse(pageProperties.defaultPageSize()))) - .stream().map(talentMapper::talentToShortTalentDTO) - .toList()); - } - - @Override - public FullTalentDTO getTalentById(long id) { - Optional talent = talentRepository.findById(id); - if (talent.isEmpty()){ - throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); - } - return talentMapper.talentToFullTalentDTO(talent.get()); - } -} \ No newline at end of file diff --git a/src/main/java/com/provedcode/user/controller/AuthenticationController.java b/src/main/java/com/provedcode/user/controller/AuthenticationController.java index 1111d1e..4bd9bd0 100644 --- a/src/main/java/com/provedcode/user/controller/AuthenticationController.java +++ b/src/main/java/com/provedcode/user/controller/AuthenticationController.java @@ -24,7 +24,7 @@ SessionInfoDTO login(Authentication authentication) { } @PostMapping("/register") - @ResponseStatus(HttpStatus.OK) + @ResponseStatus(HttpStatus.CREATED) SessionInfoDTO register(@RequestBody @Valid RegistrationDTO user) { return authenticationService.register(user); } diff --git a/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java index ae3ae5f..ed2e8bf 100644 --- a/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java +++ b/src/main/java/com/provedcode/user/model/dto/RegistrationDTO.java @@ -1,12 +1,13 @@ package com.provedcode.user.model.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; import lombok.Builder; @Builder public record RegistrationDTO( - @NotEmpty String login, + @NotEmpty @Email String login, @NotEmpty String password, @JsonProperty("first_name") @NotEmpty diff --git a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java index 72fb72f..45fc34f 100644 --- a/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java +++ b/src/main/java/com/provedcode/user/service/impl/AuthenticationServiceImpl.java @@ -1,7 +1,7 @@ package com.provedcode.user.service.impl; import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.repo.db.TalentEntityRepository; +import com.provedcode.talent.repo.TalentRepository; import com.provedcode.user.model.Role; import com.provedcode.user.model.dto.RegistrationDTO; import com.provedcode.user.model.dto.SessionInfoDTO; @@ -37,7 +37,7 @@ public class AuthenticationServiceImpl implements AuthenticationService { JwtEncoder jwtEncoder; UserInfoRepository userInfoRepository; - TalentEntityRepository talentEntityRepository; + TalentRepository talentEntityRepository; UserAuthorityRepository userAuthorityRepository; AuthorityRepository authorityRepository; PasswordEncoder passwordEncoder; @@ -53,13 +53,13 @@ public SessionInfoDTO register(RegistrationDTO user) { throw new ResponseStatusException(HttpStatus.CONFLICT, String.format("user with login = {%s} already exists", user.login())); } - Talent talent = talentEntityRepository.save( - Talent.builder() - .firstName(user.firstName()) - .lastName(user.lastName()) - .specialization(user.specialization()) - .build() - ); + Talent talent = Talent.builder() + .firstName(user.firstName()) + .lastName(user.lastName()) + .specialization(user.specialization()) + .build(); + talentEntityRepository.save(talent); + UserInfo userInfo = UserInfo.builder() .userId(talent.getId()) .login(user.login()) @@ -70,6 +70,7 @@ public SessionInfoDTO register(RegistrationDTO user) { .authority(authorityRepository.findByAuthority(Role.TALENT.toString()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "this authority does`t exist"))) .build(); + userInfo.setUserAuthorities(Set.of(userAuthority)); userAuthority.setUserInfo(userInfoRepository.save(userInfo)); userAuthorityRepository.save(userAuthority); diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index 584e38f..c8b0f61 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -1,2 +1,4 @@ spring.datasource.username=sa spring.datasource.url=jdbc:h2:mem:../sampleDB +logging.level.web=DEBUG +logging.level.sql=DEBUG \ No newline at end of file From 5d742627d508f52e240d5435a13c6931b6032ccc Mon Sep 17 00:00:00 2001 From: Ren Date: Mon, 3 Apr 2023 21:39:19 +0300 Subject: [PATCH 28/28] Delete unused files --- .../service/mock/TalentServiceMock.java | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java diff --git a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java b/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java deleted file mode 100644 index b305f3a..0000000 --- a/src/main/java/com/provedcode/talent/service/mock/TalentServiceMock.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.provedcode.talent.service.mock; - -import com.provedcode.config.PageProperties; -import com.provedcode.talent.service.TalentService; -import com.provedcode.talent.mapper.TalentMapper; -import com.provedcode.talent.model.dto.FullTalentDTO; -import com.provedcode.talent.model.dto.ShortTalentDTO; -import com.provedcode.talent.model.entity.Talent; -import com.provedcode.talent.repo.TalentRepository; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.web.server.ResponseStatusException; - -import java.util.Optional; - -import static org.springframework.http.HttpStatus.BAD_REQUEST; -import static org.springframework.http.HttpStatus.NOT_FOUND; - -//@Service -@Slf4j -@AllArgsConstructor -public class TalentServiceMock implements TalentService { - TalentMapper talentMapper; - TalentRepository talentRepository; - PageProperties pageProperties; - - @Override - public Page getTalentsPage(Optional page, Optional size) { - if (page.orElse(pageProperties.defaultPageNum()) < 0) { - throw new ResponseStatusException(BAD_REQUEST, "'page' query parameter must be greater than or equal to 0"); - } - if (size.orElse(pageProperties.defaultPageSize()) <= 0) { - throw new ResponseStatusException(BAD_REQUEST, "'size' query parameter must be greater than or equal to 1"); - } - return new PageImpl<>(talentRepository.findAll( - PageRequest.of(page.orElse(pageProperties.defaultPageNum()), size.orElse(pageProperties.defaultPageSize()))) - .stream().map(talentMapper::talentToShortTalentDTO) - .toList()); - } - - @Override - public FullTalentDTO getTalentById(long id) { - Optional talent = talentRepository.findById(id); - if (talent.isEmpty()){ - throw new ResponseStatusException(NOT_FOUND, String.format("talent with id = %d not found", id)); - } - return talentMapper.talentToFullTalentDTO(talent.get()); - } -} \ No newline at end of file