diff --git a/.gitignore b/.gitignore index a1c2a23..319a5ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,36 @@ +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/ # Compiled class file *.class diff --git a/1.txt b/1.txt new file mode 100644 index 0000000..507945c --- /dev/null +++ b/1.txt @@ -0,0 +1 @@ +orphanRemoval \ No newline at end of file diff --git a/full.sql b/full.sql new file mode 100644 index 0000000..893ce2f --- /dev/null +++ b/full.sql @@ -0,0 +1,135 @@ +BEGIN; + +DROP TABLE IF EXISTS employees_details CASCADE; +CREATE TABLE employees_details (id bigserial PRIMARY KEY, email VARCHAR(255), city varchar(255)); +INSERT INTO employees_details (email, city) VALUES +('terminator@gmail.com', 'California'), +('rembo@gmail.com', 'Atlanta'), +('corben_dallas@gmail.com', 'New York'); + +DROP TABLE IF EXISTS employees CASCADE; +CREATE TABLE employees (id bigserial PRIMARY KEY, name VARCHAR(255), details_id bigint, FOREIGN KEY (details_id) REFERENCES employees_details (id)); +INSERT INTO employees (name, details_id) VALUES +('Arnold S.', 1), +('Silvester S.', 2), +('Willis B.', 3); + +DROP TABLE IF EXISTS simple_items CASCADE; +CREATE TABLE simple_items (id bigserial PRIMARY KEY, title VARCHAR(255), price int); +INSERT INTO simple_items (title, price) VALUES +('box', 10), +('sphere', 20), +('maul', 100), +('door', 50), +('camera', 500); + +DROP TABLE IF EXISTS books CASCADE; +CREATE TABLE books (id bigserial PRIMARY KEY, title VARCHAR(255)); +INSERT INTO books (title) VALUES +('Mistborn'), +('Neverwhere'), +('Ambers Chronicles'), +('Harry Potter'), +('Lockwood & Co.'), +('Foundation Trilogy'), +('Liveship Traders Trilogy'), +('A Night in the Lonesome October'), +('Da Vinci Code'), +('Lord of the Ring'); + +DROP TABLE IF EXISTS readers CASCADE; +CREATE TABLE readers (id bigserial PRIMARY KEY, name VARCHAR(255)); +INSERT INTO readers (name) VALUES +('Alexander'), +('Bob'); + +DROP TABLE IF EXISTS books_readers CASCADE; +CREATE TABLE books_readers (book_id bigint, reader_id bigint, FOREIGN KEY (book_id) REFERENCES books (id), FOREIGN KEY (reader_id) REFERENCES readers (id)); +INSERT INTO books_readers (book_id, reader_id) VALUES +(1, 1), +(2, 1), +(3, 1), +(4, 1), +(5, 1), +(6, 1), +(7, 1), +(8, 1), +(9, 1), +(10, 1), +(1, 2), +(2, 2); + +DROP TABLE IF EXISTS universities CASCADE; +CREATE TABLE universities (id bigserial PRIMARY KEY, title VARCHAR(255)); +INSERT INTO universities (title) VALUES +('DSTU'), +('NPU'); + +DROP TABLE IF EXISTS students CASCADE; +CREATE TABLE students (id bigserial PRIMARY KEY, name VARCHAR(255), university_id bigint REFERENCES universities (id)); +INSERT INTO students (name, university_id) VALUES +('Alexander', 1), +('Bob', 2), +('John', 1); + +DROP TABLE IF EXISTS validation_beans CASCADE; +CREATE TABLE validation_beans (id bigserial PRIMARY KEY, email VARCHAR(255), priority int DEFAULT 5, postal_code varchar(6), created_at timestamp, updated_at timestamp); +INSERT INTO validation_beans (email, priority, postal_code) VALUES +('data@mail.ru', 5, '100000'); + +DROP TABLE IF EXISTS items CASCADE; +CREATE TABLE items (id bigserial PRIMARY KEY, title VARCHAR(255), price numeric(6, 2)); +INSERT INTO items (title, price) VALUES +('milk', 79.90), +('bread', 24.90), +('butter', 220.00), +('cheese', 350.55), +('coca-cola', 69.90); + +DROP TABLE IF EXISTS passports CASCADE; +CREATE TABLE passports (pserial int, pnumber int, registration_address VARCHAR(255), PRIMARY KEY (pserial, pnumber)); +INSERT INTO passports (pserial, pnumber, registration_address) VALUES +(6080, 965400, 'country.state.city.street'); + +DROP TABLE IF EXISTS citizens CASCADE; +CREATE TABLE citizens (id bigserial, name varchar(255), passport_serial int, passport_number int, CONSTRAINT fk_passport FOREIGN KEY (passport_serial, passport_number) REFERENCES passports (pserial, pnumber)); +INSERT INTO citizens (name, passport_serial, passport_number) VALUES +('Bobby', 6080, 965400); + +DROP TABLE IF EXISTS locking_items CASCADE; +CREATE TABLE locking_items (id bigserial PRIMARY KEY, value int, version int); + +DROP TABLE IF EXISTS alive_beans CASCADE; +CREATE TABLE alive_beans (id bigserial PRIMARY KEY, name varchar(255)); + +DROP TABLE IF EXISTS bottomless_boxes CASCADE; +CREATE TABLE bottomless_boxes (id bigserial PRIMARY KEY, title varchar(255)); +INSERT INTO bottomless_boxes (title) VALUES +('Big Green Box'), +('Red Box'); + +DROP TABLE IF EXISTS things CASCADE; +CREATE TABLE things (id bigserial, title varchar(255), box_id bigint REFERENCES bottomless_boxes (id)); +INSERT INTO things (title, box_id) VALUES +('toy', 1), +('ball', 1), +('pen', 2), +('pencil', null); + +DROP TABLE IF EXISTS simple_entities CASCADE; +CREATE TABLE simple_entities (id bigserial PRIMARY KEY, name varchar(255)); +INSERT INTO simple_entities (name) VALUES ('item 1'); + +DROP TABLE IF EXISTS orders CASCADE; +CREATE TABLE orders (id bigserial PRIMARY KEY, title varchar(255)); +INSERT INTO orders (title) VALUES ('order 1'); + +DROP TABLE IF EXISTS products CASCADE; +CREATE TABLE products (id bigserial PRIMARY KEY, title varchar(255), price int, order_id bigint references orders(id)); +INSERT INTO products (title, price, order_id) VALUES ('product 1', 100, 1); + +DROP TABLE IF EXISTS products_test CASCADE; +CREATE TABLE products_test (id bigserial PRIMARY KEY, title varchar(255), price int); +INSERT INTO products_test (title, price) VALUES ('product 1', 100),('product 2', 200),('product 3', 300); + +COMMIT; \ No newline at end of file diff --git a/hibernate-app.iml b/hibernate-app.iml new file mode 100644 index 0000000..81591b7 --- /dev/null +++ b/hibernate-app.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hibernate.html b/hibernate.html new file mode 100644 index 0000000..59f1544 --- /dev/null +++ b/hibernate.html @@ -0,0 +1,161 @@ + + + + + + + +
+

Hibernate

+ +

Сущности (Entities)

+ +

An entity is a lightweight persistent domain object. The primary programming artifact is the entity class. An + entity class may make use of auxiliary classes that serve as helper classes or that are used to represent the + state of the entity.

+ +

Entity Class

+ + + +

Entity State in Persistence Context

+ + + +

Entity Lifecycle

+ + +
+ + \ No newline at end of file diff --git a/lifecycle.jpg b/lifecycle.jpg new file mode 100644 index 0000000..38c46b1 Binary files /dev/null and b/lifecycle.jpg differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/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 /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="`which 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/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.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" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$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 \ + -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..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@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 "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\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/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "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%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.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 "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\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% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a63e482 --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.4.1 + + + ru.geekbrains.happy.market + happy-market + 0.0.1-SNAPSHOT + happy-market + Happy Market Project + + + 11 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.flywaydb + flyway-core + + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + true + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/questions.txt b/questions.txt new file mode 100644 index 0000000..d958b45 --- /dev/null +++ b/questions.txt @@ -0,0 +1,12 @@ +1. Чем по-умолчанию инициализируются локальные переменные? +2. Для сего нужны пакеты в Java? +3. Что такое статический импорт? +4. Что делает ключевое слово package? +5. Что такое инкапсуляция, полиморфизм и наследование? +6. Что такое абстрактный класс? +7. Что такое интерфейсы? Функциональные интерфейсы? Какие ФИ вы знаете? +Бывают ли пустые интерфейсы? +8. При импорте лкассов из пакета mypack.* будут ли импортированы классы из +пдопакетов? +9. Что такое раннее и позднее связывание? +10. \ No newline at end of file diff --git a/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java new file mode 100644 index 0000000..6637c17 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/HappyMarketApplication.java @@ -0,0 +1,25 @@ +package ru.geekbrains.happy.market; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.PropertySource; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@SpringBootApplication +@PropertySource("classpath:secured.properties") +public class HappyMarketApplication { + // Домашнее задание: + // +- // $scope.user.username = null; -- удалось сделать, только закомментировав обнуление имени, как достать имя из токена не понял. + // + // 1. Если пользователь авторизован показываем в шапке какую-нибудь информацию о нем (имя), + // если нет, там же в шапке должны боть поля логин/пароль и кнопка войти + // + // 2. Попробуйте сделать оформление заказа с привязкой к текущему пользователю + // (делать адрес доставки или что-то еще не требуется) + + public static void main(String[] args) { + SpringApplication.run(HappyMarketApplication.class, args); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/beans/Cart.java b/src/main/java/ru/geekbrains/happy/market/beans/Cart.java new file mode 100644 index 0000000..a0d416b --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/beans/Cart.java @@ -0,0 +1,57 @@ +package ru.geekbrains.happy.market.beans; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; +import org.springframework.web.context.WebApplicationContext; +import ru.geekbrains.happy.market.exceptions_handling.ResourceNotFoundException; +import ru.geekbrains.happy.market.model.OrderItem; +import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.services.ProductService; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; + +@Component +@RequiredArgsConstructor +@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) +@Data +public class Cart { + private final ProductService productService; + private List items; + private int totalPrice; + + @PostConstruct + public void init() { + this.items = new ArrayList<>(); + } + + public void addToCart(Long id) { + for (OrderItem o : items) { + if (o.getProduct().getId().equals(id)) { + o.incrementQuantity(); + recalculate(); + return; + } + } + Product p = productService.findProductById(id).orElseThrow(() -> new ResourceNotFoundException("Unable to find product with id: " + id + " (add to cart)")); + OrderItem orderItem = new OrderItem(p); + items.add(orderItem); + recalculate(); + } + + public void clear() { + items.clear(); + recalculate(); + } + + public void recalculate() { + totalPrice = 0; + for (OrderItem o : items) { + totalPrice += o.getPrice(); + } + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java b/src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java new file mode 100644 index 0000000..7cd6902 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/beans/JwtTokenUtil.java @@ -0,0 +1,60 @@ +package ru.geekbrains.happy.market.beans; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Component +public class JwtTokenUtil { + @Value("${jwt.secret}") + private String secret; + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + List rolesList = userDetails.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.toList()); + claims.put("roles", rolesList); + + Date issuedDate = new Date(); + Date expiredDate = new Date(issuedDate.getTime() + 20 * 60 * 1000); // todo + return Jwts.builder() + .setClaims(claims) + .setSubject(userDetails.getUsername()) + .setIssuedAt(issuedDate) + .setExpiration(expiredDate) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + public List getRoles(String token) { + return getClaimFromToken(token, (Function>) claims -> claims.get("roles", List.class)); + } + + private T getClaimFromToken(String token, Function claimsResolver) { + Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + private Claims getAllClaimsFromToken(String token) { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java b/src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java new file mode 100644 index 0000000..7819842 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/configs/JwtRequestFilter.java @@ -0,0 +1,57 @@ +package ru.geekbrains.happy.market.configs; + +import io.jsonwebtoken.ExpiredJwtException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import ru.geekbrains.happy.market.beans.JwtTokenUtil; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +@Slf4j +public class JwtRequestFilter extends OncePerRequestFilter { +// private final UserDetailsService userDetailsService; + private final JwtTokenUtil jwtTokenUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authHeader = request.getHeader("Authorization"); + + String username = null; + String jwt = null; + if (authHeader != null && authHeader.startsWith("Bearer ")) { + jwt = authHeader.substring(7); + try { + username = jwtTokenUtil.getUsernameFromToken(jwt); + } catch (ExpiredJwtException e) { + log.debug("The token is expired"); +// String error = JsonUtils.convertObjectToJson(new BookServiceError(HttpStatus.UNAUTHORIZED.value(), "Jwt is expired")); +// response.sendError(HttpServletResponse.SC_UNAUTHORIZED, error); +// return; + } + } + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { +// UserDetails userDetails = userDetailsService.loadUserByUsername(username); +// UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); +// token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); +// SecurityContextHolder.getContext().setAuthentication(token); + + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, jwtTokenUtil.getRoles(jwt).stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())); + SecurityContextHolder.getContext().setAuthentication(token); + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java b/src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java new file mode 100644 index 0000000..7b17581 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/configs/SecurityConfig.java @@ -0,0 +1,47 @@ +package ru.geekbrains.happy.market.configs; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +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.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@EnableWebSecurity +@RequiredArgsConstructor +@Slf4j +public class SecurityConfig extends WebSecurityConfigurerAdapter { + private final JwtRequestFilter jwtRequestFilter; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .csrf().disable() + .authorizeRequests() + .antMatchers("/api/v1/**").authenticated() + .antMatchers("/h2-console/**").permitAll() + .anyRequest().permitAll() + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .headers().frameOptions().disable(); + http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Override + @Bean + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java b/src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java new file mode 100644 index 0000000..37e5873 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/controllers/AuthController.java @@ -0,0 +1,37 @@ +package ru.geekbrains.happy.market.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import ru.geekbrains.happy.market.beans.JwtTokenUtil; +import ru.geekbrains.happy.market.dto.JwtRequest; +import ru.geekbrains.happy.market.dto.JwtResponse; +import ru.geekbrains.happy.market.exceptions_handling.MarketError; +import ru.geekbrains.happy.market.services.UserService; + +@RestController +@RequiredArgsConstructor +public class AuthController { + private final UserService userService; + private final JwtTokenUtil jwtTokenUtil; + private final AuthenticationManager authenticationManager; + + @PostMapping("/auth") + public ResponseEntity createAuthToken(@RequestBody JwtRequest authRequest) { + try { + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())); + } catch (BadCredentialsException ex) { + return new ResponseEntity<>(new MarketError(HttpStatus.UNAUTHORIZED.value(), "Incorrect username or password"), HttpStatus.UNAUTHORIZED); + } + UserDetails userDetails = userService.loadUserByUsername(authRequest.getUsername()); + String token = jwtTokenUtil.generateToken(userDetails); + return ResponseEntity.ok(new JwtResponse(token)); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/CartController.java b/src/main/java/ru/geekbrains/happy/market/controllers/CartController.java new file mode 100644 index 0000000..a0eea5c --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/controllers/CartController.java @@ -0,0 +1,33 @@ +package ru.geekbrains.happy.market.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.geekbrains.happy.market.beans.Cart; +import ru.geekbrains.happy.market.dto.CartDto; + +import java.security.Principal; + +@RestController +@RequestMapping("/api/v1/cart") +@RequiredArgsConstructor +public class CartController { + private final Cart cart; + + @GetMapping + public CartDto getCart() { + return new CartDto(cart); + } + + @GetMapping("/add/{id}") + public void addToCart(@PathVariable Long id) { + cart.addToCart(id); + } + + @GetMapping("/clear") + public void clearCart() { + cart.clear(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java new file mode 100644 index 0000000..627a4dc --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/controllers/ProductController.java @@ -0,0 +1,61 @@ +package ru.geekbrains.happy.market.controllers; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.*; +import ru.geekbrains.happy.market.dto.ProductDto; +import ru.geekbrains.happy.market.exceptions_handling.MarketError; +import ru.geekbrains.happy.market.exceptions_handling.ResourceNotFoundException; +import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.repositories.ProductRepository; +import ru.geekbrains.happy.market.repositories.specifications.ProductSpecifications; +import ru.geekbrains.happy.market.services.ProductService; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@RestController +@RequestMapping("/api/v1/products") +@RequiredArgsConstructor +public class ProductController { + private final ProductService productService; + + @GetMapping + public Page findAllProducts( + @RequestParam MultiValueMap params, + @RequestParam(name = "p", defaultValue = "1") Integer page + ) { + if (page < 1) { + page = 1; + } + + return productService.findAll(ProductSpecifications.build(params), page, 4); + } + + @GetMapping("/{id}") + public ProductDto findProductById(@PathVariable Long id) { + return productService.findProductDtoById(id).orElseThrow(() -> new ResourceNotFoundException("Product with id: " + id + " doens't exist")); + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Product saveNewProduct(@RequestBody Product product) { + product.setId(null); + return productService.saveOrUpdate(product); + } + + @PutMapping + public Product updateProduct(@RequestBody Product product) { + return productService.saveOrUpdate(product); + } + + @DeleteMapping("/{id}") + public void updateProduct(@PathVariable Long id) { + productService.deleteById(id); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/CartDto.java b/src/main/java/ru/geekbrains/happy/market/dto/CartDto.java new file mode 100644 index 0000000..aa2b7d6 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/CartDto.java @@ -0,0 +1,20 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.geekbrains.happy.market.beans.Cart; + +import java.util.List; +import java.util.stream.Collectors; + +@NoArgsConstructor +@Data +public class CartDto { + private List items; + private int totalPrice; + + public CartDto(Cart cart) { + this.totalPrice = cart.getTotalPrice(); + this.items = cart.getItems().stream().map(OrderItemDto::new).collect(Collectors.toList()); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java b/src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java new file mode 100644 index 0000000..c3807ce --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/JwtRequest.java @@ -0,0 +1,9 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; + +@Data +public class JwtRequest { + private String username; + private String password; +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java b/src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java new file mode 100644 index 0000000..884304b --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/JwtResponse.java @@ -0,0 +1,10 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class JwtResponse { + private String token; +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java b/src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java new file mode 100644 index 0000000..cd82f36 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/OrderItemDto.java @@ -0,0 +1,22 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.geekbrains.happy.market.model.OrderItem; +import ru.geekbrains.happy.market.model.Product; + +@NoArgsConstructor +@Data +public class OrderItemDto { + private String productTitle; + private int quantity; + private int pricePerProduct; + private int price; + + public OrderItemDto(OrderItem orderItem) { + this.productTitle = orderItem.getProduct().getTitle(); + this.quantity = orderItem.getQuantity(); + this.pricePerProduct = orderItem.getPricePerProduct(); + this.price = orderItem.getPrice(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java b/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java new file mode 100644 index 0000000..0d5f29d --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/dto/ProductDto.java @@ -0,0 +1,19 @@ +package ru.geekbrains.happy.market.dto; + +import lombok.Data; +import lombok.NoArgsConstructor; +import ru.geekbrains.happy.market.model.Product; + +@Data +@NoArgsConstructor +public class ProductDto { + private Long id; + private String title; + private int price; + + public ProductDto(Product p) { + this.id = p.getId(); + this.title = p.getTitle(); + this.price = p.getPrice(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java new file mode 100644 index 0000000..7a646df --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ExceptionControllerAdvice.java @@ -0,0 +1,18 @@ +package ru.geekbrains.happy.market.exceptions_handling; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +@Slf4j +public class ExceptionControllerAdvice { + @ExceptionHandler + public ResponseEntity handleResourceNotFoundException(ResourceNotFoundException e) { + log.error(e.getMessage()); + MarketError err = new MarketError(HttpStatus.NOT_FOUND.value(), e.getMessage()); + return new ResponseEntity<>(err, HttpStatus.NOT_FOUND); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java new file mode 100644 index 0000000..68dbb3d --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/MarketError.java @@ -0,0 +1,18 @@ +package ru.geekbrains.happy.market.exceptions_handling; + +import lombok.Data; + +import java.util.Date; + +@Data +public class MarketError { + private int status; + private String message; + private Date timestamp; + + public MarketError(int status, String message) { + this.status = status; + this.message = message; + this.timestamp = new Date(); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java new file mode 100644 index 0000000..eddf8c9 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/exceptions_handling/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package ru.geekbrains.happy.market.exceptions_handling; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/OrderItem.java b/src/main/java/ru/geekbrains/happy/market/model/OrderItem.java new file mode 100644 index 0000000..34c31ff --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/OrderItem.java @@ -0,0 +1,47 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@NoArgsConstructor +@Data +@Entity +@Table(name = "order_items") +public class OrderItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "product_id") + private Product product; + + @Column(name = "quantity") + private int quantity; + + @Column(name = "price_per_product") + private int pricePerProduct; + + @Column(name = "price") + private int price; + + public OrderItem(Product product) { + this.product = product; + this.quantity = 1; + this.pricePerProduct = product.getPrice(); + this.price = this.pricePerProduct; + } + + public void incrementQuantity() { + quantity++; + price = quantity * pricePerProduct; + } + + public void decrementQuantity() { + quantity--; + price = quantity * pricePerProduct; + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/Product.java b/src/main/java/ru/geekbrains/happy/market/model/Product.java new file mode 100644 index 0000000..30b0cf7 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/Product.java @@ -0,0 +1,34 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "products") +@Data +@NoArgsConstructor +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "title") + private String title; + + @Column(name = "price") + private int price; + + @Column(name = "created_at") + @CreationTimestamp + private LocalDateTime createdAt; + + @Column(name = "updated_at") + @UpdateTimestamp + private LocalDateTime updatedAt; +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/Role.java b/src/main/java/ru/geekbrains/happy/market/model/Role.java new file mode 100644 index 0000000..0bbc61a --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/Role.java @@ -0,0 +1,29 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Data +@Table(name = "roles") +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name") + private String name; + + @CreationTimestamp + @Column(name = "created_at") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/src/main/java/ru/geekbrains/happy/market/model/User.java b/src/main/java/ru/geekbrains/happy/market/model/User.java new file mode 100644 index 0000000..c0259b3 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/model/User.java @@ -0,0 +1,42 @@ +package ru.geekbrains.happy.market.model; + +import lombok.Data; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.time.LocalDateTime; +import java.util.Collection; + +@Entity +@Data +@Table(name = "users") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "username") + private String username; + + @Column(name = "password") + private String password; + + @Column(name = "email") + private String email; + + @ManyToMany + @JoinTable(name = "users_roles", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id")) + private Collection roles; + + @CreationTimestamp + @Column(name = "created_at") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} \ No newline at end of file diff --git a/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java b/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java new file mode 100644 index 0000000..fbf9957 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/repositories/ProductRepository.java @@ -0,0 +1,12 @@ +package ru.geekbrains.happy.market.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.stereotype.Repository; +import ru.geekbrains.happy.market.model.Product; + +import java.util.List; + +@Repository +public interface ProductRepository extends JpaRepository, JpaSpecificationExecutor { +} diff --git a/src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java b/src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java new file mode 100644 index 0000000..2791c47 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/repositories/UserRepository.java @@ -0,0 +1,12 @@ +package ru.geekbrains.happy.market.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.geekbrains.happy.market.model.User; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); +} diff --git a/src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java b/src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java new file mode 100644 index 0000000..f18f965 --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/repositories/specifications/ProductSpecifications.java @@ -0,0 +1,40 @@ +package ru.geekbrains.happy.market.repositories.specifications; + +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.MultiValueMap; +import ru.geekbrains.happy.market.model.Product; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.math.BigDecimal; +import java.util.Optional; + +public class ProductSpecifications { + private static Specification priceGreaterOrEqualsThan(int minPrice) { + return (Specification) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.greaterThanOrEqualTo(root.get("price"), minPrice); + } + + private static Specification priceLesserOrEqualsThan(int maxPrice) { + return (Specification) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThanOrEqualTo(root.get("price"), maxPrice); + } + + private static Specification titleLike(String titlePart) { + return (Specification) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("title"), String.format("%%%s%%", titlePart)); + } + + public static Specification build(MultiValueMap params) { + Specification spec = Specification.where(null); + if (params.containsKey("min_price") && !params.getFirst("min_price").isBlank()) { + spec = spec.and(ProductSpecifications.priceGreaterOrEqualsThan(Integer.parseInt(params.getFirst("min_price")))); + } + if (params.containsKey("max_price") && !params.getFirst("max_price").isBlank()) { + spec = spec.and(ProductSpecifications.priceLesserOrEqualsThan(Integer.parseInt(params.getFirst("max_price")))); + } + if (params.containsKey("title") && !params.getFirst("title").isBlank()) { + spec = spec.and(ProductSpecifications.titleLike(params.getFirst("title"))); + } + return spec; + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/services/ProductService.java b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java new file mode 100644 index 0000000..38269fe --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/services/ProductService.java @@ -0,0 +1,41 @@ +package ru.geekbrains.happy.market.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import ru.geekbrains.happy.market.dto.ProductDto; +import ru.geekbrains.happy.market.model.Product; +import ru.geekbrains.happy.market.repositories.ProductRepository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ProductService { + private final ProductRepository productRepository; + + public Optional findProductById(Long id) { + return productRepository.findById(id); + } + + public Optional findProductDtoById(Long id) { + return productRepository.findById(id).map(ProductDto::new); + } + + public Page findAll(Specification spec, int page, int pageSize) { + return productRepository.findAll(spec, PageRequest.of(page - 1, pageSize)).map(ProductDto::new); + } + + public Product saveOrUpdate(Product product) { + return productRepository.save(product); + } + + public void deleteById(Long id) { + productRepository.deleteById(id); + } +} diff --git a/src/main/java/ru/geekbrains/happy/market/services/UserService.java b/src/main/java/ru/geekbrains/happy/market/services/UserService.java new file mode 100644 index 0000000..789132d --- /dev/null +++ b/src/main/java/ru/geekbrains/happy/market/services/UserService.java @@ -0,0 +1,38 @@ +package ru.geekbrains.happy.market.services; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.geekbrains.happy.market.model.Role; +import ru.geekbrains.happy.market.model.User; +import ru.geekbrains.happy.market.repositories.UserRepository; + +import java.util.Collection; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class UserService implements UserDetailsService { + private final UserRepository userRepository; + + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); + } + + @Override + @Transactional + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(String.format("User '%s' not found", username))); + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), mapRolesToAuthorities(user.getRoles())); + } + + private Collection mapRolesToAuthorities(Collection roles) { + return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..475da03 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,20 @@ +server: + port: 8189 + servlet: + context-path: /happy +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:mydatabase;MODE=PostgreSQL + username: sa + password: + jpa: + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.H2Dialect + h2: + console: + enabled: true + settings: + web-allow-others: false diff --git a/src/main/resources/db/migration/V1__init.sql b/src/main/resources/db/migration/V1__init.sql new file mode 100644 index 0000000..7703cf7 --- /dev/null +++ b/src/main/resources/db/migration/V1__init.sql @@ -0,0 +1,72 @@ +create table users ( + id bigserial primary key, + username varchar(30) not null unique, + password varchar(80) not null, + email varchar(50) unique, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +create table roles ( + id bigserial primary key, + name varchar(50) not null unique, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +CREATE TABLE users_roles ( + user_id bigint not null references users (id), + role_id bigint not null references roles (id), + primary key (user_id, role_id) +); + +insert into roles (name) +values +('ROLE_USER'), +('ROLE_ADMIN'); + +insert into users (username, password, email) +values +('bob', '$2a$04$Fx/SX9.BAvtPlMyIIqqFx.hLY2Xp8nnhpzvEEVINvVpwIPbA3v/.i', 'bob_johnson@gmail.com'), +('john', '$2a$04$Fx/SX9.BAvtPlMyIIqqFx.hLY2Xp8nnhpzvEEVINvVpwIPbA3v/.i', 'john_johnson@gmail.com'); + +insert into users_roles (user_id, role_id) +values +(1, 1), +(2, 2); + +create table products ( + id bigserial primary key, + title varchar(255), + price int, + created_at timestamp default current_timestamp, + updated_at timestamp default current_timestamp +); + +insert into products (title, price) +values +('Bread', 24), +('Milk', 65), +('Cheese', 320), +('Cheese2', 322), +('Cheese3', 323), +('Cheese4', 324), +('Cheese5', 325), +('Cheese6', 326), +('Cheese7', 327), +('Cheese8', 328), +('Cheese9', 328), +('Cheese10', 328), +('Cheese11', 328), +('Cheese12', 328), +('Cheese13', 328), +('Cheese14', 328), +('Cheese15', 328); + +create table order_items ( + id bigserial primary key, + title varchar(255), + quantity int, + price_per_item int, + price int +); \ No newline at end of file diff --git a/src/main/resources/secured.properties b/src/main/resources/secured.properties new file mode 100644 index 0000000..e816be0 --- /dev/null +++ b/src/main/resources/secured.properties @@ -0,0 +1 @@ +jwt.secret=ifh9v8eyf9843hf3498hg934gft093whf09hG&@G*&Gd983wgf398gf973g837fg \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..f189566 --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + +
+

Happy Market

+
+ +
+ +
+
+

Вы вошли как {{user.username}}

+
+

Фильтр

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+ +

Список товаров

+ + + + + + + + + + + + + + + + + +
НазваниеЦена
{{p.title}}{{p.price}} + + + +
+ + + +

Корзина

+ + + + + + + + + + + + + + + + + + + + +
НазваниеКоличествоЦена
{{oi.productTitle}}{{oi.quantity}}{{oi.quantity + ' x ' + oi.pricePerProduct + ' = ' + oi.price}}
Итого{{Cart.totalPrice}}
+ +
+
+ + \ No newline at end of file diff --git a/src/main/resources/static/index.js b/src/main/resources/static/index.js new file mode 100644 index 0000000..52b798c --- /dev/null +++ b/src/main/resources/static/index.js @@ -0,0 +1,100 @@ +angular.module('app', []).controller('indexController', function ($scope, $http) { + const contextPath = 'http://localhost:8189/happy'; + $scope.authorized = false; + + $scope.fillTable = function (pageIndex = 1) { + $http({ + url: contextPath + '/api/v1/products', + method: 'GET', + params: { + title: $scope.filter ? $scope.filter.title : null, + min_price: $scope.filter ? $scope.filter.min_price : null, + max_price: $scope.filter ? $scope.filter.max_price : null, + p: pageIndex + } + }).then(function (response) { + $scope.ProductsPage = response.data; + + let minPageIndex = pageIndex - 2; + if (minPageIndex < 1) { + minPageIndex = 1; + } + + let maxPageIndex = pageIndex + 2; + if (maxPageIndex > $scope.ProductsPage.totalPages) { + maxPageIndex = $scope.ProductsPage.totalPages; + } + + $scope.PaginationArray = $scope.generatePagesIndexes(minPageIndex, maxPageIndex); + }); + }; + + $scope.showCart = function () { + $http({ + url: contextPath + '/api/v1/cart', + method: 'GET' + }).then(function (response) { + $scope.Cart = response.data; + }); + }; + + $scope.generatePagesIndexes = function(startPage, endPage) { + let arr = []; + for (let i = startPage; i < endPage + 1; i++) { + arr.push(i); + } + return arr; + } + + // $scope.submitCreateNewProduct = function () { + // $http.post(contextPath + '/products', $scope.newProduct) + // .then(function (response) { + // $scope.newProduct = null; + // $scope.fillTable(); + // }); + // }; + + $scope.deleteProductById = function (productId) { + $http.delete(contextPath + '/api/v1/products/' + productId) + .then(function (response) { + $scope.fillTable(); + }); + } + + $scope.addToCart = function (productId) { + $http.get(contextPath + '/api/v1/cart/add/' + productId) + .then(function (response) { + $scope.showCart(); + }); + } + + $scope.clearCart = function () { + $http.get(contextPath + '/api/v1/cart/clear') + .then(function (response) { + $scope.showCart(); + }); + } + + $scope.tryToAuth = function () { + $http.post(contextPath + '/auth', $scope.user) + .then(function successCallback(response) { + if (response.data.token) { + $http.defaults.headers.common.Authorization = 'Bearer ' + response.data.token; + // $scope.user.username = null; + $scope.user.password = null; + $scope.authorized = true; + $scope.fillTable(); + $scope.getUsername(); + } + }, function errorCallback(response) { + window.alert("Error"); + }); + }; + + $scope.getUsername = function () { + return $scope.Authorization.getUsername(); + }; + + // $scope.fillTable(); + // $scope.showCart(); +}); \ No newline at end of file diff --git a/src/test/java/ru/geekbrains/happy/market/HappyMarketApplicationTests.java b/src/test/java/ru/geekbrains/happy/market/HappyMarketApplicationTests.java new file mode 100644 index 0000000..3555bd7 --- /dev/null +++ b/src/test/java/ru/geekbrains/happy/market/HappyMarketApplicationTests.java @@ -0,0 +1,13 @@ +package ru.geekbrains.happy.market; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class HappyMarketApplicationTests { + + @Test + void contextLoads() { + } + +}