From 7a15613907bf9c50d0ddf23e4f07d1cc76376ef7 Mon Sep 17 00:00:00 2001 From: Tran Hoang Quan Date: Thu, 22 Feb 2024 15:00:10 +0700 Subject: [PATCH] finish home page --- .gitignore | 33 ++ .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .mvn/wrapper/maven-wrapper.properties | 2 + mvnw | 308 +++++++++++++ mvnw.cmd | 205 +++++++++ pom.xml | 94 ++++ .../project/VegetableProjectApplication.java | 13 + .../project/config/SecurityConfig.java | 70 +++ .../project/controller/HomeController.java | 183 ++++++++ .../controller/RegistrationController.java | 77 ++++ .../boot/vegetable/project/dto/BlogDto.java | 13 + .../vegetable/project/dto/ProductDto.java | 14 + .../boot/vegetable/project/dto/UserDto.java | 26 ++ .../boot/vegetable/project/entity/Banner.java | 22 + .../boot/vegetable/project/entity/Blog.java | 66 +++ .../project/entity/BlogCategory.java | 23 + .../vegetable/project/entity/Category.java | 22 + .../vegetable/project/entity/Product.java | 32 ++ .../boot/vegetable/project/entity/Role.java | 30 ++ .../boot/vegetable/project/entity/User.java | 49 +++ .../project/entity/VerificationToken.java | 41 ++ .../event/RegistrationCompleteEvent.java | 21 + .../RegistrationCompleteListener.java | 42 ++ .../project/repository/BannerRepository.java | 12 + .../project/repository/BlogRepository.java | 12 + .../repository/CategoryRepository.java | 10 + .../project/repository/ProductRepository.java | 17 + .../project/repository/UserRepository.java | 12 + .../VerificationTokenRepository.java | 12 + .../security/CustomUserDetailService.java | 22 + .../project/security/CustomUserDetails.java | 68 +++ .../project/service/BannerService.java | 15 + .../project/service/BlogService.java | 16 + .../project/service/CategoryService.java | 16 + .../project/service/ProductService.java | 21 + .../project/service/UserService.java | 9 + .../service/VerificationTokenService.java | 16 + .../service/implement/BannerServiceImp.java | 45 ++ .../service/implement/BlogServiceImp.java | 65 +++ .../service/implement/CategoryServiceImp.java | 46 ++ .../service/implement/ProductServiceImp.java | 74 ++++ .../service/implement/UserServiceImp.java | 35 ++ .../VerificationTokenServiceImp.java | 49 +++ .../project/utility/ApplicationUrl.java | 10 + .../vegetable/project/utility/ImageUtils.java | 47 ++ .../vegetable/project/utility/SendMail.java | 53 +++ .../utility/TokenExpirationTimeCalculus.java | 17 + src/main/resources/application.yml | 24 + src/main/resources/static/css/footer.css | 57 +++ src/main/resources/static/css/header.css | 128 ++++++ src/main/resources/static/css/home.css | 410 ++++++++++++++++++ src/main/resources/static/css/login.css | 33 ++ src/main/resources/static/css/main.css | 11 + src/main/resources/static/css/register.css | 5 + src/main/resources/static/img/banner.jpg | Bin 0 -> 16004 bytes src/main/resources/static/img/logo.png | Bin 0 -> 1022 bytes .../static/img/payment-item.png.webp | Bin 0 -> 2836 bytes src/main/resources/static/js/home.js | 97 +++++ .../resources/templates/fragments/footer.html | 54 +++ .../templates/fragments/fragments.html | 110 +++++ src/main/resources/templates/home.html | 216 +++++++++ src/main/resources/templates/login.html | 44 ++ src/main/resources/templates/register.html | 61 +++ .../VegetableProjectApplicationTests.java | 13 + .../VerificationTokenRepositoryTest.java | 20 + 65 files changed, 3368 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/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplication.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/config/SecurityConfig.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/HomeController.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/RegistrationController.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/BlogDto.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/ProductDto.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/UserDto.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Banner.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Blog.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/BlogCategory.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Category.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Product.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Role.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/User.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/VerificationToken.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/event/RegistrationCompleteEvent.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/event/listener/RegistrationCompleteListener.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BannerRepository.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BlogRepository.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/CategoryRepository.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/ProductRepository.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/UserRepository.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepository.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetailService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetails.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BannerService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BlogService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/CategoryService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/ProductService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/UserService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/VerificationTokenService.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BannerServiceImp.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BlogServiceImp.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/CategoryServiceImp.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/ProductServiceImp.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/UserServiceImp.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/VerificationTokenServiceImp.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ApplicationUrl.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ImageUtils.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/SendMail.java create mode 100644 src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/TokenExpirationTimeCalculus.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/static/css/footer.css create mode 100644 src/main/resources/static/css/header.css create mode 100644 src/main/resources/static/css/home.css create mode 100644 src/main/resources/static/css/login.css create mode 100644 src/main/resources/static/css/main.css create mode 100644 src/main/resources/static/css/register.css create mode 100644 src/main/resources/static/img/banner.jpg create mode 100644 src/main/resources/static/img/logo.png create mode 100644 src/main/resources/static/img/payment-item.png.webp create mode 100644 src/main/resources/static/js/home.js create mode 100644 src/main/resources/templates/fragments/footer.html create mode 100644 src/main/resources/templates/fragments/fragments.html create mode 100644 src/main/resources/templates/home.html create mode 100644 src/main/resources/templates/login.html create mode 100644 src/main/resources/templates/register.html create mode 100644 src/test/java/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplicationTests.java create mode 100644 src/test/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepositoryTest.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..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..66df285 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# 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 + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -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 "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); 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 2>/dev/null; \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 + +# 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/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# 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. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +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 "$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 + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@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 Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@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 WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_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 WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_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('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@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..8ee7f9f --- /dev/null +++ b/pom.xml @@ -0,0 +1,94 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.1 + + + org.ecommerce.spring.boot + vegetable-project + 0.0.1-SNAPSHOT + vegetable-project + vegetable-project + + 17 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity6 + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + com.mysql + mysql-connector-j + runtime + + + 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/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplication.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplication.java new file mode 100644 index 0000000..f2f470c --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplication.java @@ -0,0 +1,13 @@ +package org.ecommerce.spring.boot.vegetable.project; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class VegetableProjectApplication { + + public static void main(String[] args) { + SpringApplication.run(VegetableProjectApplication.class, args); + } + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/config/SecurityConfig.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/config/SecurityConfig.java new file mode 100644 index 0000000..12565b7 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/config/SecurityConfig.java @@ -0,0 +1,70 @@ +package org.ecommerce.spring.boot.vegetable.project.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +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.AbstractHttpConfigurer; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Autowired + private UserDetailsService userDetailsService; + + private final static String[] paths = { + "/**", + "/error", + "/login", + "/registration/**", + "/img/**", + "/css/**" + }; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests( + author -> author.requestMatchers(paths).permitAll() + .anyRequest().authenticated() + ) + .formLogin( + form -> form.loginPage("/login") + .loginProcessingUrl("/login") + .usernameParameter("email") + .defaultSuccessUrl("/") + .permitAll() + ) + .logout( + log -> log.invalidateHttpSession(true) + .clearAuthentication(true) + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/") + .permitAll() + ) + .build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(11); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); + authenticationProvider.setUserDetailsService(userDetailsService); + authenticationProvider.setPasswordEncoder(passwordEncoder()); + return authenticationProvider; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/HomeController.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/HomeController.java new file mode 100644 index 0000000..9b8a933 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/HomeController.java @@ -0,0 +1,183 @@ +package org.ecommerce.spring.boot.vegetable.project.controller; + +import org.ecommerce.spring.boot.vegetable.project.dto.BlogDto; +import org.ecommerce.spring.boot.vegetable.project.dto.ProductDto; +import org.ecommerce.spring.boot.vegetable.project.entity.Banner; +import org.ecommerce.spring.boot.vegetable.project.entity.Blog; +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.ecommerce.spring.boot.vegetable.project.entity.Product; +import org.ecommerce.spring.boot.vegetable.project.service.BannerService; +import org.ecommerce.spring.boot.vegetable.project.service.BlogService; +import org.ecommerce.spring.boot.vegetable.project.service.CategoryService; +import org.ecommerce.spring.boot.vegetable.project.service.ProductService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.ModelAndView; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +@Controller +@RequestMapping({"/", "/home"}) +public class HomeController { + + @Autowired + private CategoryService categoryService; + + @Autowired + private ProductService productService; + + @Autowired + private BannerService bannerService; + + @Autowired + private BlogService blogService; + + @GetMapping("/") + public ModelAndView home() throws ParseException { + ModelAndView modelAndView = new ModelAndView("home"); + List categories = categoryService.getCategoryList(); + List banners = bannerService.getBannerList(); + List blogs = blogService.getBlogList(); + modelAndView.addObject("categories", categories); + modelAndView.addObject("banners", banners); + modelAndView.addObject("blogs", blogs); + return modelAndView; + } + + @GetMapping("/login") + public String loginForm() { + return "login"; + } + + @PostMapping("/addCategory") + public ResponseEntity addCategory(@RequestParam("name") String name, @RequestParam("image")MultipartFile file) throws IOException { + categoryService.addCategory(name, file); + return ResponseEntity.ok("success"); + } + + @GetMapping("/getCategoryList") + public ModelAndView getCategoryList() { + ModelAndView modelAndView = new ModelAndView("home"); + List categories = categoryService.getCategoryList(); + modelAndView.addObject("categories", categories); + return modelAndView; + } + + @GetMapping("/getCategoryImage/{id}") + public ResponseEntity getCategoryImage(@PathVariable Long id) { + byte[] image = categoryService.getCategoryImage(id); + return ResponseEntity.status(HttpStatus.OK) + .contentType(MediaType.valueOf("image/png")) + .body(image); + } + + @PostMapping("/addProduct") + public ResponseEntity addProduct(@ModelAttribute ProductDto productDto) throws IOException { + Product p = productService.addProduct(productDto); + return ResponseEntity.ok("add product success"); + } + + @GetMapping("/getProductList/{pageNumber}") + @ResponseBody + public List getProductList(@PathVariable Integer pageNumber) { + ModelAndView modelAndView = new ModelAndView("home"); + List products = productService.getProductList(pageNumber); + return products; + } + + @GetMapping("/getProductImage/{id}") + public ResponseEntity getProductImage(@PathVariable Long id) { + byte[] image = productService.getProductImage(id); + return ResponseEntity.status(HttpStatus.OK) + .contentType(MediaType.valueOf("image/png")) + .body(image); + } + + @GetMapping("/getProductListByCategory/{categoryName}/{pageNumber}") + @ResponseBody + public List getProductListByCategory(@PathVariable String categoryName, @PathVariable Integer pageNumber) { + List products = productService.getProductListByCategory(categoryName, pageNumber); + return products; + } + + @GetMapping("/getTotalPages/{categoryName}") + @ResponseBody + public Long getTotalPages(@PathVariable String categoryName) { + long totalPage = productService.getTotalPages(categoryName); + return totalPage; + } + + @GetMapping("/getTotalPagesAll") + @ResponseBody + public Long getTotalPagesAll() { + long totalPage = productService.getTotalPagesAll(); + return totalPage; + } + + @PostMapping("/addBanner") + public ResponseEntity addBanner(@RequestParam String name, @RequestParam MultipartFile image) throws IOException { + bannerService.addBanner(name, image); + return ResponseEntity.ok("add banner successs"); + } + + @GetMapping("/getBannerList") + public ModelAndView getBannerList() { + ModelAndView modelAndView = new ModelAndView("home"); + List banners = bannerService.getBannerList(); + modelAndView.addObject("banners", banners); + return modelAndView; + } + + @GetMapping("/getBannerImage/{id}") + public ResponseEntity getBannerImageById(@PathVariable Long id) { + byte[] image = bannerService.getBannerImageById(id); + return ResponseEntity.status(HttpStatus.OK) + .contentType(MediaType.valueOf("image/png")) + .body(image); + } + + @PostMapping("/addBlog") + public ResponseEntity addBlog(@ModelAttribute BlogDto blogDto) throws IOException { + blogService.addBlog(blogDto); + return ResponseEntity.ok("add blog success"); + } + + @GetMapping("/getBlogImage/{id}") + public ResponseEntity getBlogImageById(@PathVariable Long id) { + byte[] image = blogService.getBlogImageById(id); + return ResponseEntity.status(HttpStatus.OK) + .contentType(MediaType.valueOf("image/png")) + .body(image); + } + +} + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/RegistrationController.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/RegistrationController.java new file mode 100644 index 0000000..e97b8d4 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/controller/RegistrationController.java @@ -0,0 +1,77 @@ +package org.ecommerce.spring.boot.vegetable.project.controller; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.ecommerce.spring.boot.vegetable.project.dto.UserDto; +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.ecommerce.spring.boot.vegetable.project.entity.VerificationToken; +import org.ecommerce.spring.boot.vegetable.project.event.RegistrationCompleteEvent; +import org.ecommerce.spring.boot.vegetable.project.service.UserService; +import org.ecommerce.spring.boot.vegetable.project.service.VerificationTokenService; +import org.ecommerce.spring.boot.vegetable.project.utility.ApplicationUrl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.ModelAndView; + +import javax.naming.Binding; +import java.util.Optional; + +@Controller +@RequestMapping("/registration") +public class RegistrationController { + + @Autowired + private UserService userService; + + @Autowired + private VerificationTokenService verificationTokenService; + + @Autowired + private ApplicationEventPublisher publisher; + + @GetMapping("/register-form") + public ModelAndView showRegistrationForm() { + ModelAndView modelAndView = new ModelAndView("register"); + modelAndView.addObject("user", new UserDto()); + return modelAndView; + } + + @PostMapping("/register") + public String registerUser(@Valid @ModelAttribute UserDto userDto, + BindingResult result, Model model, HttpServletRequest request) { + if(!userDto.getPassword().equals(userDto.getConfirmPassword())) { + return "redirect:/registration/register-form?invalidc_f"; + } + if(result.hasErrors()) { + model.addAttribute("user", userDto); + } + System.out.println(result); + User user = userService.registerUser(userDto); + publisher.publishEvent(new RegistrationCompleteEvent(user, ApplicationUrl.getUrl(request))); + return "redirect:/registration/register-form?success"; + } + + @GetMapping("/verifyEmail") + public String verifyEmail(@RequestParam String token) { + Optional verificationTokenOptional = + verificationTokenService.findByToken(token); + if(verificationTokenOptional.isPresent() && verificationTokenOptional.get().getUser().getEnabled()) { + return "redirect:/login?verified"; + } + String result = verificationTokenService.validateVerificationToken(token); + switch (result.toLowerCase()) { + case "valid": + return "redirect:/login?valid"; + case "invalid": + return "redirect:/error?invalid"; + default: + return "redirect:/error?expired"; + } + } + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/BlogDto.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/BlogDto.java new file mode 100644 index 0000000..c11ff96 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/BlogDto.java @@ -0,0 +1,13 @@ +package org.ecommerce.spring.boot.vegetable.project.dto; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +@Data +public class BlogDto { + private String title; + private String content; + private MultipartFile image; + private Long userId; + private String blogCategory; +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/ProductDto.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/ProductDto.java new file mode 100644 index 0000000..53bdb04 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/ProductDto.java @@ -0,0 +1,14 @@ +package org.ecommerce.spring.boot.vegetable.project.dto; + +import lombok.Data; +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.springframework.web.multipart.MultipartFile; + +@Data +public class ProductDto { + private String name; + private double cost; + private MultipartFile image; + private String categoryName; + private String description; +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/UserDto.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/UserDto.java new file mode 100644 index 0000000..2e43553 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/dto/UserDto.java @@ -0,0 +1,26 @@ +package org.ecommerce.spring.boot.vegetable.project.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class UserDto { + private Long id; + + @NotEmpty + private String firstName; + + @NotEmpty + private String lastName; + + @NotEmpty(message = "Email should not be empty") + private String email; + + @NotEmpty(message = "Password should not be empty") + private String password; + + @NotEmpty(message = "Confirm password should not be empty") + private String confirmPassword; + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Banner.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Banner.java new file mode 100644 index 0000000..6d3dff2 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Banner.java @@ -0,0 +1,22 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Banner { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + @Lob() + @Column(length = 1000000000) + private byte[] image; +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Blog.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Blog.java new file mode 100644 index 0000000..8c9e6ed --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Blog.java @@ -0,0 +1,66 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; +import java.util.List; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Blog { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String title; + + @Temporal(TemporalType.TIMESTAMP) + @Column(nullable = false) + @DateTimeFormat(pattern = "MM-dd, YYYY") + private Date createDate; + + @Lob + @Column(length = 1000000000) + private String content; + + @Lob + @Column(length = 1000000000) + private byte[] image; + + @ManyToOne + @JoinColumn( + name = "user_id", + referencedColumnName = "id" + ) + private User user; + + @ManyToMany( + fetch = FetchType.EAGER, + cascade = CascadeType.ALL + ) + @JoinTable( + name = "blog_categories", + joinColumns = @JoinColumn( + name = "blog_id", + referencedColumnName = "id" + ), + inverseJoinColumns = @JoinColumn( + name = "blogCategory_id", + referencedColumnName = "id" + ) + ) + private List blogCategories; + + @PrePersist + private void onCreate() { + createDate = new Date(); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/BlogCategory.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/BlogCategory.java new file mode 100644 index 0000000..d7e77ab --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/BlogCategory.java @@ -0,0 +1,23 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class BlogCategory { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + + public BlogCategory(String name) { + this.name = name; + } +} \ No newline at end of file diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Category.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Category.java new file mode 100644 index 0000000..c290bee --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Category.java @@ -0,0 +1,22 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + @Lob() + @Column(length = 1000000000) + private byte[] image; +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Product.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Product.java new file mode 100644 index 0000000..040b5df --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Product.java @@ -0,0 +1,32 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + private double cost; + private String description; + @Lob + @Column(length = 1000000000) + private byte[] image; + + @ManyToOne + @JoinColumn( + name = "category_id", + referencedColumnName = "id" + ) + private Category category; + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Role.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Role.java new file mode 100644 index 0000000..50880bc --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/Role.java @@ -0,0 +1,30 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Collate; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Role { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @Column( + nullable = false + ) + private String name; + + public Role(String name) { + this.name = name; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/User.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/User.java new file mode 100644 index 0000000..329afd3 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/User.java @@ -0,0 +1,49 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String firstName; + + @Column(nullable = false) + private String lastName; + + @Column(nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @ManyToMany( + fetch = FetchType.EAGER, + cascade = CascadeType.ALL + ) + @JoinTable( + name = "user_roles", + joinColumns = @JoinColumn( + name = "user_id", + referencedColumnName = "id" + ), + inverseJoinColumns = @JoinColumn( + name = "role_id", + referencedColumnName = "id" + ) + ) + private List roles = new ArrayList<>(); + + private Boolean enabled = false; +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/VerificationToken.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/VerificationToken.java new file mode 100644 index 0000000..23617a8 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/entity/VerificationToken.java @@ -0,0 +1,41 @@ +package org.ecommerce.spring.boot.vegetable.project.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.ecommerce.spring.boot.vegetable.project.utility.TokenExpirationTimeCalculus; + +import java.util.Date; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class VerificationToken { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String token; + private Date expirationTime; + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn( + name = "user_id", + referencedColumnName = "id" + ) + private User user; + + public VerificationToken(User user, String token) { + this.user = user; + this.token = token; + this.expirationTime = TokenExpirationTimeCalculus.getExpirationTime(); + } + + public VerificationToken(String token) { + this.token = token; + this.expirationTime = TokenExpirationTimeCalculus.getExpirationTime(); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/event/RegistrationCompleteEvent.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/event/RegistrationCompleteEvent.java new file mode 100644 index 0000000..6bc9c53 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/event/RegistrationCompleteEvent.java @@ -0,0 +1,21 @@ +package org.ecommerce.spring.boot.vegetable.project.event; + + +import lombok.Getter; +import lombok.Setter; +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.springframework.context.ApplicationEvent; + +@Getter +@Setter +public class RegistrationCompleteEvent extends ApplicationEvent { + + private User user; + private String confirmationUrl; + + public RegistrationCompleteEvent(User user, String confirmationUrl) { + super(user); + this.user = user; + this.confirmationUrl = confirmationUrl; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/event/listener/RegistrationCompleteListener.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/event/listener/RegistrationCompleteListener.java new file mode 100644 index 0000000..be7e7ae --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/event/listener/RegistrationCompleteListener.java @@ -0,0 +1,42 @@ +package org.ecommerce.spring.boot.vegetable.project.event.listener; + +import jakarta.mail.MessagingException; +import lombok.extern.slf4j.Slf4j; +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.ecommerce.spring.boot.vegetable.project.event.RegistrationCompleteEvent; +import org.ecommerce.spring.boot.vegetable.project.service.VerificationTokenService; +import org.ecommerce.spring.boot.vegetable.project.utility.SendMail; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; + +import java.io.UnsupportedEncodingException; +import java.util.UUID; + +@Component +@Slf4j +public class RegistrationCompleteListener implements ApplicationListener { + + @Autowired + private VerificationTokenService verificationTokenService; + + @Autowired + private SendMail sendMail; + + @Override + public void onApplicationEvent(RegistrationCompleteEvent event) { + User user = event.getUser(); + String token = UUID.randomUUID().toString(); + verificationTokenService.saveVerificationTokenForUser(user, token); + + String verifyMailUrl = event.getConfirmationUrl() + "/registration/verifyEmail?token=" + token; + + try { + sendMail.sendVerificationEmail(user, verifyMailUrl); + } catch (MessagingException | UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + log.info("Click the link to verify your account, {}", verifyMailUrl); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BannerRepository.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BannerRepository.java new file mode 100644 index 0000000..ec14355 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BannerRepository.java @@ -0,0 +1,12 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.ecommerce.spring.boot.vegetable.project.entity.Banner; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BannerRepository extends JpaRepository { + List findTop2ByOrderByIdDesc(); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BlogRepository.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BlogRepository.java new file mode 100644 index 0000000..142be24 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/BlogRepository.java @@ -0,0 +1,12 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.ecommerce.spring.boot.vegetable.project.entity.Blog; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BlogRepository extends JpaRepository { + List findTop3ByOrderByIdDesc(); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/CategoryRepository.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/CategoryRepository.java new file mode 100644 index 0000000..cdacb93 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/CategoryRepository.java @@ -0,0 +1,10 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryRepository extends JpaRepository { + Category getByName(String categoryName); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/ProductRepository.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/ProductRepository.java new file mode 100644 index 0000000..1ccdf07 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/ProductRepository.java @@ -0,0 +1,17 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.ecommerce.spring.boot.vegetable.project.entity.Product; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ProductRepository extends JpaRepository { + Page getProductListByCategory(Category category, Pageable pageable); + + Page findAllByCategory(Category category, Pageable pageable); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/UserRepository.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/UserRepository.java new file mode 100644 index 0000000..53d9178 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/UserRepository.java @@ -0,0 +1,12 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepository.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepository.java new file mode 100644 index 0000000..330498c --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepository.java @@ -0,0 +1,12 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.ecommerce.spring.boot.vegetable.project.entity.VerificationToken; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface VerificationTokenRepository extends JpaRepository { + Optional findByToken(String token); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetailService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetailService.java new file mode 100644 index 0000000..9a9a8b1 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetailService.java @@ -0,0 +1,22 @@ +package org.ecommerce.spring.boot.vegetable.project.security; + +import org.ecommerce.spring.boot.vegetable.project.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +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; + +@Service +public class CustomUserDetailService implements UserDetailsService { + + @Autowired + private UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + return userRepository.findByEmail(email) + .map(CustomUserDetails::new) + .orElseThrow(()-> new UsernameNotFoundException("User not found")); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetails.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetails.java new file mode 100644 index 0000000..435c404 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/security/CustomUserDetails.java @@ -0,0 +1,68 @@ +package org.ecommerce.spring.boot.vegetable.project.security; + +import lombok.Data; +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +@Data +public class CustomUserDetails implements UserDetails { + + private String userName; + private String password; + private Boolean isEnabled; + private List authorities; + + CustomUserDetails(User user) { + this.userName = user.getEmail(); + this.password = user.getPassword(); + this.isEnabled = user.getEnabled(); + this.authorities = + Arrays.stream(user.getRoles().toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return userName; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return isEnabled; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BannerService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BannerService.java new file mode 100644 index 0000000..a9f3b51 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BannerService.java @@ -0,0 +1,15 @@ +package org.ecommerce.spring.boot.vegetable.project.service; + +import org.ecommerce.spring.boot.vegetable.project.entity.Banner; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +public interface BannerService { + void addBanner(String name, MultipartFile image) throws IOException; + + List getBannerList(); + + byte[] getBannerImageById(Long id); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BlogService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BlogService.java new file mode 100644 index 0000000..b27d8c9 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/BlogService.java @@ -0,0 +1,16 @@ +package org.ecommerce.spring.boot.vegetable.project.service; + +import org.ecommerce.spring.boot.vegetable.project.dto.BlogDto; +import org.ecommerce.spring.boot.vegetable.project.entity.Blog; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +public interface BlogService { + void addBlog(BlogDto blogDto) throws IOException; + + List getBlogList() throws ParseException; + + byte[] getBlogImageById(Long id); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/CategoryService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/CategoryService.java new file mode 100644 index 0000000..b23ba59 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/CategoryService.java @@ -0,0 +1,16 @@ +package org.ecommerce.spring.boot.vegetable.project.service; + +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +public interface CategoryService { + + void addCategory(String name, MultipartFile file) throws IOException; + + List getCategoryList(); + + byte[] getCategoryImage(Long id); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/ProductService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/ProductService.java new file mode 100644 index 0000000..f8cebb3 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/ProductService.java @@ -0,0 +1,21 @@ +package org.ecommerce.spring.boot.vegetable.project.service; + +import org.ecommerce.spring.boot.vegetable.project.dto.ProductDto; +import org.ecommerce.spring.boot.vegetable.project.entity.Product; + +import java.io.IOException; +import java.util.List; + +public interface ProductService { + Product addProduct(ProductDto productDto) throws IOException; + + List getProductList(Integer pageNumber); + + byte[] getProductImage(Long id); + + List getProductListByCategory(String categoryName, Integer pageNumber); + + Long getTotalPages(String categoryName); + + long getTotalPagesAll(); +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/UserService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/UserService.java new file mode 100644 index 0000000..8c26599 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/UserService.java @@ -0,0 +1,9 @@ +package org.ecommerce.spring.boot.vegetable.project.service; + +import org.ecommerce.spring.boot.vegetable.project.dto.UserDto; +import org.ecommerce.spring.boot.vegetable.project.entity.User; + +public interface UserService { + User registerUser(UserDto userDto); + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/VerificationTokenService.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/VerificationTokenService.java new file mode 100644 index 0000000..1c80e5e --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/VerificationTokenService.java @@ -0,0 +1,16 @@ +package org.ecommerce.spring.boot.vegetable.project.service; + +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.ecommerce.spring.boot.vegetable.project.entity.VerificationToken; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +public interface VerificationTokenService { + void saveVerificationTokenForUser(User user, String token); + + Optional findByToken(String token); + + String validateVerificationToken(String token); + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BannerServiceImp.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BannerServiceImp.java new file mode 100644 index 0000000..6ebb66c --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BannerServiceImp.java @@ -0,0 +1,45 @@ +package org.ecommerce.spring.boot.vegetable.project.service.implement; + +import org.ecommerce.spring.boot.vegetable.project.entity.Banner; +import org.ecommerce.spring.boot.vegetable.project.repository.BannerRepository; +import org.ecommerce.spring.boot.vegetable.project.service.BannerService; +import org.ecommerce.spring.boot.vegetable.project.utility.ImageUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@Service +public class BannerServiceImp implements BannerService { + + @Autowired + private ImageUtils imageUtils; + + @Autowired + private BannerRepository bannerRepository; + + @Override + public void addBanner(String name, MultipartFile image) throws IOException { + Banner banner = Banner.builder() + .name(name) + .image(imageUtils.compressImage(image.getBytes())) + .build(); + bannerRepository.save(banner); + } + + @Override + public List getBannerList() { + List banners = bannerRepository.findTop2ByOrderByIdDesc(); + return banners; + } + + @Override + public byte[] getBannerImageById(Long id) { + Banner banner = bannerRepository.findById(id).get(); + return imageUtils.decompressImage(banner.getImage()); + } + + +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BlogServiceImp.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BlogServiceImp.java new file mode 100644 index 0000000..927a901 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/BlogServiceImp.java @@ -0,0 +1,65 @@ +package org.ecommerce.spring.boot.vegetable.project.service.implement; + +import org.ecommerce.spring.boot.vegetable.project.dto.BlogDto; +import org.ecommerce.spring.boot.vegetable.project.entity.Blog; +import org.ecommerce.spring.boot.vegetable.project.entity.BlogCategory; +import org.ecommerce.spring.boot.vegetable.project.repository.BlogRepository; +import org.ecommerce.spring.boot.vegetable.project.repository.UserRepository; +import org.ecommerce.spring.boot.vegetable.project.service.BlogService; +import org.ecommerce.spring.boot.vegetable.project.utility.ImageUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class BlogServiceImp implements BlogService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private ImageUtils imageUtils; + + @Autowired + private BlogRepository blogRepository; + + @Override + public void addBlog(BlogDto blogDto) throws IOException { + Blog blog = Blog.builder() + .title(blogDto.getTitle()) + .content(blogDto.getContent()) + .image(imageUtils.compressImage(blogDto.getImage().getBytes())) + .user(userRepository.findById(blogDto.getUserId()).get()) + .blogCategories(blogCategoryChangeList(blogDto.getBlogCategory())) + .build(); + blogRepository.save(blog); + } + + @Override + public List getBlogList() { + List blogs = blogRepository.findTop3ByOrderByIdDesc(); + return blogs; + } + + @Override + public byte[] getBlogImageById(Long id) { + Blog blog = blogRepository.findById(id).get(); + return imageUtils.decompressImage(blog.getImage()); + } + + private List blogCategoryChangeList(String blogCategoryString) { + List blogCategories = + Arrays.asList(blogCategoryString.split(",")).stream().map(categoryName -> new BlogCategory(categoryName.trim())) + .collect(Collectors.toList()); + return blogCategories; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/CategoryServiceImp.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/CategoryServiceImp.java new file mode 100644 index 0000000..ef6beba --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/CategoryServiceImp.java @@ -0,0 +1,46 @@ +package org.ecommerce.spring.boot.vegetable.project.service.implement; + +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.ecommerce.spring.boot.vegetable.project.repository.CategoryRepository; +import org.ecommerce.spring.boot.vegetable.project.service.CategoryService; +import org.ecommerce.spring.boot.vegetable.project.utility.ImageUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@Service +public class CategoryServiceImp implements CategoryService { + + @Autowired + private ImageUtils imageUtils; + + @Autowired + private CategoryRepository categoryRepository; + + @Override + public void addCategory(String name, MultipartFile file) throws IOException { + Category category = Category.builder() + .name(name) + .image(imageUtils.compressImage(file.getBytes())) + .build(); + categoryRepository.save(category); + } + + @Override + public List getCategoryList() { + List categories = categoryRepository.findAll(); + for(Category c : categories) { + c.setImage(imageUtils.decompressImage(c.getImage())); + } + return categories; + } + + @Override + public byte[] getCategoryImage(Long id) { + Category category = categoryRepository.findById(id).get(); + return imageUtils.decompressImage(category.getImage()); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/ProductServiceImp.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/ProductServiceImp.java new file mode 100644 index 0000000..8e3094e --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/ProductServiceImp.java @@ -0,0 +1,74 @@ +package org.ecommerce.spring.boot.vegetable.project.service.implement; + +import org.ecommerce.spring.boot.vegetable.project.dto.ProductDto; +import org.ecommerce.spring.boot.vegetable.project.entity.Category; +import org.ecommerce.spring.boot.vegetable.project.entity.Product; +import org.ecommerce.spring.boot.vegetable.project.repository.CategoryRepository; +import org.ecommerce.spring.boot.vegetable.project.repository.ProductRepository; +import org.ecommerce.spring.boot.vegetable.project.service.ProductService; +import org.ecommerce.spring.boot.vegetable.project.utility.ImageUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.List; + +@Service +public class ProductServiceImp implements ProductService { + + @Autowired + private ProductRepository productRepository; + + @Autowired + private ImageUtils imageUtils; + + @Autowired + private CategoryRepository categoryRepository; + + @Override + public Product addProduct(ProductDto productDto) throws IOException { + Product product = Product.builder() + .name(productDto.getName()) + .cost(productDto.getCost()) + .description(productDto.getDescription()) + .image(imageUtils.compressImage(productDto.getImage().getBytes())) + .category(categoryRepository.getByName(productDto.getCategoryName())) + .build(); + return productRepository.save(product); + } + + @Override + public List getProductList(Integer pageNumber) { + return productRepository.findAll(PageRequest.of(pageNumber, 8)).getContent(); + } + + @Override + public byte[] getProductImage(Long id) { + return + imageUtils.decompressImage( + productRepository.findById(id).get().getImage() + ); + } + + @Override + public List getProductListByCategory(String categoryName, Integer pageNumber) { + Category category = categoryRepository.getByName(categoryName); + Pageable page = PageRequest.of(pageNumber, 8); + return productRepository.getProductListByCategory(category, page).getContent(); + } + + @Override + public Long getTotalPages(String categoryName) { + Category category = categoryRepository.getByName(categoryName); + Long totalPage = (long) productRepository.findAllByCategory(category, PageRequest.of(0, 8)).getTotalPages(); + return totalPage; + } + + @Override + public long getTotalPagesAll() { + Long totalPage = (long) productRepository.findAll(PageRequest.of(0, 8)).getTotalPages(); + return totalPage; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/UserServiceImp.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/UserServiceImp.java new file mode 100644 index 0000000..5919e2e --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/UserServiceImp.java @@ -0,0 +1,35 @@ +package org.ecommerce.spring.boot.vegetable.project.service.implement; + +import org.ecommerce.spring.boot.vegetable.project.dto.UserDto; +import org.ecommerce.spring.boot.vegetable.project.entity.Role; +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.ecommerce.spring.boot.vegetable.project.repository.UserRepository; +import org.ecommerce.spring.boot.vegetable.project.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Arrays; + +@Service +public class UserServiceImp implements UserService { + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private UserRepository userRepository; + + @Override + public User registerUser(UserDto userDto) { + User user = User.builder() + .firstName(userDto.getFirstName()) + .lastName(userDto.getLastName()) + .email(userDto.getEmail()) + .password(passwordEncoder.encode(userDto.getPassword())) + .roles(Arrays.asList(new Role("USER"))) + .enabled(false) + .build(); + return userRepository.save(user); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/VerificationTokenServiceImp.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/VerificationTokenServiceImp.java new file mode 100644 index 0000000..4104793 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/service/implement/VerificationTokenServiceImp.java @@ -0,0 +1,49 @@ +package org.ecommerce.spring.boot.vegetable.project.service.implement; + +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.ecommerce.spring.boot.vegetable.project.entity.VerificationToken; +import org.ecommerce.spring.boot.vegetable.project.repository.UserRepository; +import org.ecommerce.spring.boot.vegetable.project.repository.VerificationTokenRepository; +import org.ecommerce.spring.boot.vegetable.project.service.VerificationTokenService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Calendar; +import java.util.Optional; + +@Service +public class VerificationTokenServiceImp implements VerificationTokenService { + + @Autowired + private VerificationTokenRepository verificationTokenRepository; + + @Autowired + private UserRepository userRepository; + + @Override + public void saveVerificationTokenForUser(User user, String token) { + VerificationToken verificationToken = new VerificationToken(user, token); + verificationTokenRepository.save(verificationToken); + } + + @Override + public Optional findByToken(String token) { + return verificationTokenRepository.findByToken(token); + } + + @Override + public String validateVerificationToken(String token) { + Optional verificationToken = verificationTokenRepository.findByToken(token); + if(verificationToken.isEmpty()) + return "invalid"; + User user = verificationToken.get().getUser(); + Calendar calendar = Calendar.getInstance(); + if(verificationToken.get().getExpirationTime().getTime() - calendar.getTime().getTime() <= 0) { + verificationTokenRepository.delete(verificationToken.get()); + return "expired"; + } + user.setEnabled(true); + userRepository.save(user); + return "valid"; + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ApplicationUrl.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ApplicationUrl.java new file mode 100644 index 0000000..d801033 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ApplicationUrl.java @@ -0,0 +1,10 @@ +package org.ecommerce.spring.boot.vegetable.project.utility; + +import jakarta.servlet.http.HttpServletRequest; + +public class ApplicationUrl { + public static String getUrl(HttpServletRequest request) { + String appUrl = request.getRequestURL().toString(); + return appUrl.replace(request.getServletPath(), ""); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ImageUtils.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ImageUtils.java new file mode 100644 index 0000000..7ae7dc4 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/ImageUtils.java @@ -0,0 +1,47 @@ +package org.ecommerce.spring.boot.vegetable.project.utility; + +import org.springframework.stereotype.Component; + +import java.io.ByteArrayOutputStream; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +@Component +public class ImageUtils { + public byte[] compressImage(byte[] data) { + Deflater deflater = new Deflater(); + deflater.setLevel(Deflater.BEST_COMPRESSION); + deflater.setInput(data); + deflater.finish(); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length); + byte[] tmp = new byte[4*1024]; + while (!deflater.finished()) { + int size = deflater.deflate(tmp); + outputStream.write(tmp, 0, size); + } + try { + outputStream.close(); + } catch (Exception ignored) { + } + return outputStream.toByteArray(); + } + + + + public byte[] decompressImage(byte[] data) { + Inflater inflater = new Inflater(); + inflater.setInput(data); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length); + byte[] tmp = new byte[4*1024]; + try { + while (!inflater.finished()) { + int count = inflater.inflate(tmp); + outputStream.write(tmp, 0, count); + } + outputStream.close(); + } catch (Exception ignored) { + } + return outputStream.toByteArray(); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/SendMail.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/SendMail.java new file mode 100644 index 0000000..0b077df --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/SendMail.java @@ -0,0 +1,53 @@ +package org.ecommerce.spring.boot.vegetable.project.utility; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.ecommerce.spring.boot.vegetable.project.entity.User; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; + +@Component +public class SendMail { + + @Autowired + private JavaMailSender mailSender; + + public void sendPasswordResetVerificationEmail(User user,String url) throws MessagingException, UnsupportedEncodingException { + String subject = "Password Reset Request Verification"; + String senderName = "Users Verification Service"; + String mailContent = "

Hi, "+ user.getFirstName()+ ",

"+ + "

You recently requested to reset your password,"+"" + + "Please, follow the link below to complete the action.

"+ + "Reset password"+ + "

Users Registration Portal Service"; + emailMessage(subject, senderName, mailContent, user); + } + + public void sendVerificationEmail(User user,String url) throws MessagingException, UnsupportedEncodingException { + String subject = "Verify your account"; + String senderName = "Users Verification Service"; + String mailContent = "

Hi, "+ user.getFirstName()+ ",

"+ + "

CLick follow link to verify your account,"+"" + + "Verify account"+ + "

Users Registration Portal Service"; + emailMessage(subject, senderName, mailContent, user); + } + + private void emailMessage(String subject, String senderName, + String mailContent, User theUser) + throws MessagingException, UnsupportedEncodingException { + MimeMessage message = mailSender.createMimeMessage(); + var messageHelper = new MimeMessageHelper(message); + messageHelper.setFrom("hoangquanph0204@gmail.com", senderName); + messageHelper.setTo(theUser.getEmail()); + messageHelper.setSubject(subject); + messageHelper.setText(mailContent, true); + mailSender.send(message); + } +} diff --git a/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/TokenExpirationTimeCalculus.java b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/TokenExpirationTimeCalculus.java new file mode 100644 index 0000000..49ee997 --- /dev/null +++ b/src/main/java/org/ecommerce/spring/boot/vegetable/project/utility/TokenExpirationTimeCalculus.java @@ -0,0 +1,17 @@ +package org.ecommerce.spring.boot.vegetable.project.utility; + +import org.springframework.stereotype.Component; + +import java.util.Calendar; +import java.util.Date; + +public class TokenExpirationTimeCalculus { + private static int EXPIRATION_TIME = 10; + + public static Date getExpirationTime() { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(new Date().getTime()); + calendar.add(Calendar.MINUTE, EXPIRATION_TIME); + return new Date(calendar.getTime().getTime()); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..915d787 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,24 @@ + +server: + port: 8080 +spring: + datasource: + url: jdbc:mysql://localhost:3306/vegetable-project + username: root + password: 01229050402a + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: true + mail: + host: smtp.gmail.com + port: 587 + username: hoangquanph0204@gmail.com + password: hspuorvvliiqgbgr + properties: + mail: + smtp: + auth: true + starttls: + enable: true diff --git a/src/main/resources/static/css/footer.css b/src/main/resources/static/css/footer.css new file mode 100644 index 0000000..c4f4dd0 --- /dev/null +++ b/src/main/resources/static/css/footer.css @@ -0,0 +1,57 @@ + +*, ::after, ::before { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.footer-wrapper { + background-color: rgb(243,246,250); + height: max-content; +} + +.footer-left-icon { + margin-bottom: 20px; +} + +.footer-left-information { + display: block; + font-size: 16px; + line-height: 25px; + margin: 5px 0; + color: #1c1c1c; +} + +.footer-left-contact { + font-weight: bold; + font-size: 16px; +} + +.footer-left-contact-item { + font-size: 22px; + color: black; + padding: 10px 15px; + border-radius: 40px; + background-color: white; + } + +.footer-left-contact-item:hover { + background-color: #7fad39; + color: white; +} + +.footer-right-description { + display: block; + margin: 8px 0 12px 0; + font-size: 13px; + color: #1c1c1c; +} + +.footer-right-email { + width: 120px; +} + +.footer-down-image { + display: flex; + margin-left: auto; +} \ No newline at end of file diff --git a/src/main/resources/static/css/header.css b/src/main/resources/static/css/header.css new file mode 100644 index 0000000..2cd20b9 --- /dev/null +++ b/src/main/resources/static/css/header.css @@ -0,0 +1,128 @@ + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.header-top { + background-color: rgb(245,245,245); + height: 46px; + font-size: 14px; +} + +.header-top-left, .header-top-right { + margin: 0; + list-style-type: none; + padding: 12px 0 13px; +} + +.header-top-item { + display: inline-block; +} + +.header-top-left__mail { + margin-right: 40px ; +} + +.header-top-left__mail-icon { + margin-right: 3px; +} + +.header-top-left__mail::after, .header-top-item-separate::after { + position: absolute; + top: 12px; + margin: 0 20px; + width: 0.5px; + height: 20px; + background-color: black; + opacity: 0.1; + content: ""; +} + +.header-top-right { + width: max-content; + float: left; +} + +.header-top-right-icon { + color: black; + margin-right: 20px; +} + +.header-top-item-separate { + margin-right: 40px; + color: black; +} + +.header-top-login { + display: inline-block; + color: black; + text-decoration: none; +} + +.login-icon { + margin-right: 2px; +} + +.header-menu-nav { + background-color: white !important; +} + +.header-menu-item-btn { + color: black; + font-size: 14px; + font-weight: bold; + letter-spacing: 1px; +} + +.header-menu-item-btn:hover { + color: #7fad39; +} + +.active { + color: #7fad39; +} + +.header-menu-icon { + color: black; + font-size: 17px; + margin-right: 5px; +} + +.header-user-action, .header-menu-page { + background-color: black; + padding-bottom: 3px; + padding-top: 3px; + display: none; + border-radius: unset; + position: absolute; + box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.1); + z-index: 1; +} + +.header-top-item:hover .header-user-action { + display: block; +} + +.header-menu-page-wrapper:hover .header-menu-page { + display: block; +} + + +.header-user-action-item { + padding-top: 5px; + padding-bottom: 5px; + border: none; + line-height: 17px; + letter-spacing: 1px; + color: white; + background-color: black; + text-decoration: none; +} + +.header-user-action-item:hover { + color: #7fad39; + background-color: black; +} + diff --git a/src/main/resources/static/css/home.css b/src/main/resources/static/css/home.css new file mode 100644 index 0000000..eb58826 --- /dev/null +++ b/src/main/resources/static/css/home.css @@ -0,0 +1,410 @@ + + +*, ::after, ::before { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.mh-12 { + margin: 0 12px; +} + + +.search-content { + margin-top: 80px !important; +} + +.search-content-department-wrapper { +} + +.search-content-department { + font-weight: bold; + padding: 10px 30px; + font-size: 18px; + border-radius: unset; + background-color: #7fad39; + color: white; + border: none; +} + +.search-content-department:hover { + background-color: #7fad39; +} + +.search-content-department:visited { + background-color: #7fad39; +} + +.search-content-department:active { + background-color: #7fad39 !important; +} + +.dropdown-toggle::after { + display: inline-block; + margin-left: 20px; + vertical-align: .255em; + content: ""; + border: .3em solid; + border-right-color: transparent; + border-bottom: 0 none; + border-left-color: transparent; +} + +.search-content-dropdown-list { + padding: 10px 30px; + font-size: 16px; + border-radius: unset; + color: white; +} + +.search-content-dropdown-item { + padding: 8px 10px; + width: 202px; + border-bottom: none; + border-top: none; +} + +.search-bar { + height: 48px; +} + +.search-bar-input { + font-size: 14px; + border-radius: unset; + margin-right: 0 !important; + padding-left: 20px; + +} + +.search-bar-input::placeholder { + color: rgba(0, 0, 0, 0.4); + margin-left: 10px; +} + +.search-bar-input:focus { + outline: none !important; +} + +.search-bar-submit { + border-radius: unset; + width: 25%; + background-color: #7fad39; + color: white; + font-weight: bold; + font-size: 14px; + border: none; +} + +.search-bar-submit:hover { + background-color: #7fad39; + color: white; +} + +.phone-wrapper { + height: 48px; + padding-left: 50px; +} + +.phone-icon-wrapper { + content: ""; + padding: 10px 15px; + background-color: rgba(0,0,0,0.04); + border-radius: 50%; +} + +.phone-icon { + color: #7fad39; + font-size: 18px; + height: fit-content; +} + +.phone-number-wrapper { + padding-left: 20px; + font-size: 18px; +} + +.banner-img { + +} + +.banner-text { + position: absolute; + top: 90px; + left: 80px; +} + +.banner-text-category { + letter-spacing: 2px; + color: #7fad39; + line-height: 14px; + font-weight: bold; + font-size: 14px; +} + +.banner-text-title { + font-size: 46px; + font-weight: bold; + margin: 8px 0; +} + +.banner-text-description { + color: rgba(0, 0, 0, 0.5); + font-size: 16px; + margin-bottom: 40px; + display: block; +} + +.banner-text-btn { + padding: 8px 25px; + color: white; + font-weight: bold; + border-radius: unset; + font-size: 14px; + letter-spacing: 1px; + background-color: #7fad39; +} + +.banner-text-btn:hover { + background: #7fad39; + color: white; +} + +.category-bar { + flex-wrap: nowrap; + overflow: scroll; +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +.category-bar::-webkit-scrollbar { + display: none; +} + + +.category-bar { + width: 100%; + display: flex; + overflow-x: auto; /* Use 'auto' to enable scrolling */ + scroll-behavior: smooth; /* Add smooth scrolling behavior */ + white-space: nowrap; + transition: all 0.5s ease-in-out; /* Apply transition effect to all properties */ +} + +.category-wrapper { + height: 270px; + position: relative; +} + +.category-bar-wrapper { + margin-bottom: 60px !important; +} + +.category-item-title-wrapper { + position: absolute; + bottom: 0; + right: 0; + left: 0; + margin: 0 auto; +} + +.category-item-title { + position: absolute; + bottom: 15px; + right: 27px; + left: 0; + margin: 0 auto; + width: fit-content; + background-color: white; + font-size: 18px; + letter-spacing: 1px; + font-weight: bold; + padding: 8px 40px; + text-decoration: unset; + color: black; + transform: translateX(6px); +} + +.category-item-img { + height: 100%; +} + +.category-bar-navigate-wrapper { + position: relative; + z-index: 1; +} + +.category-bar-navigate { + height: max-content; + width: max-content; + position: absolute; + top: 140px; + bottom: 0; + margin: auto 0; + z-index: 1; + padding: 23px 6px; + border: 1px solid rgba(0, 0, 0, 0.1); + background-color: white; +} + +.category-bar-navigate-left { + left: -60px; +} + +.category-bar-navigate-right { + right: -60px; +} + +.featured-product-title { + font-weight: bold; + font-size: 36px; +} + +.featured-product-title:after { + display: block; + height: 4px; + width: 80px; + background: #7fad39; + content: ""; + margin: 5px auto 0; +} + +.feature-product-navigate { + width: max-content; + margin: 0 auto; + margin-bottom: 10px; +} + +.feature-product-navigate-link { + font-size: 18px; + text-decoration: none; + color: black; + display: inline-block; + cursor: pointer; +} + +.choose::after { + content: ""; + display: block; + width: 100%; + height: 2px; + background-color: #7fad39; + margin-top: 2px; +} + +.feature-product-item-wrapper { + +} + +.feature-product-item { + margin-top: 35px; +} + +.feature-product-item-text { + line-height: 18px; + padding-top: 10px; +} + +.feature-product-item-title { + font-size: 16px; + font-weight: unset; + text-decoration: none; + color: black; + padding: 8px 0; +} + +.feature-product-item-img { + max-width: 100%; + height: 260px; +} + +.feature-product-item-cost { + font-size: 18px; + font-weight: bold; +} + +.product-page > .page-link { + color: rgba(0, 0, 0, 0.7); +} + +.active > .page-link { + background-color: #7fad39; + border: #7fad39; + color: white; +} + +.page-link { + cursor: pointer; +} + +.home_banner_wrapper { + height: 262px; +} + +.home_banner { + +} + +.home_banner_image { + width: 100%; + height: 100%; +} + +.blog-item { + +} + +.blog-item-image { + width: 100%; + height: 258px; +} + +.createDate { + +} + +.createDate-icon { + display: inline-block; + font-size: 16px; + color: rgba(0, 0, 0, 0.4); +} + +.createDate-time { + display: inline-block; + font-size: 16px; + color: rgba(0, 0, 0, 0.3); + margin: 3px 0 0 0; +} + +.blog-item-title { + display: -webkit-box; + line-height: 20px; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + font-size: 20px; + font-weight: bold; + width: 100%; + clear: both; + color: black; + text-decoration: none; + overflow: hidden; + } + +.blog-item-content { + display: -webkit-box; + line-height: 18px; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + font-size: 16px; + width: 100%; + clear: both; + color: rgba(0, 0, 0, 0.6); + overflow: hidden; +} + + + + + + + + diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css new file mode 100644 index 0000000..5495fca --- /dev/null +++ b/src/main/resources/static/css/login.css @@ -0,0 +1,33 @@ + +.body-login { + width: 100vw; + height: 100vh; + background-color: #7fad39; +} + +.login-container-wrapper { + background-color: white; + width: 600px; + height: 300px; + border-radius: 30px; + box-shadow: 0 0 6px 1px rgba(0, 0, 0, 0.7); + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; + margin: auto; + display: flex; + align-items: center; + justify-content: center; +} + + +.login-container { + display: flex; + flex-direction: column; + justify-content: center; +} + + + diff --git a/src/main/resources/static/css/main.css b/src/main/resources/static/css/main.css new file mode 100644 index 0000000..48afefb --- /dev/null +++ b/src/main/resources/static/css/main.css @@ -0,0 +1,11 @@ + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.bg-active { + background: rgb(127,173,57) !important; +} + diff --git a/src/main/resources/static/css/register.css b/src/main/resources/static/css/register.css new file mode 100644 index 0000000..d78fc3c --- /dev/null +++ b/src/main/resources/static/css/register.css @@ -0,0 +1,5 @@ + +.login-container-wrapper { + min-height: 440px; + width: 700px; +} \ No newline at end of file diff --git a/src/main/resources/static/img/banner.jpg b/src/main/resources/static/img/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa610d62bd0f65819028cd8b0586581d91dfbd09 GIT binary patch literal 16004 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 jf^xsB=)@V4p?m;UC;;l#4*&q7Ir9TB{mNb>p^ zzEfT*FCR%>aF7>qzK}0pz9h+;qj(|Sy(7Q6CCLXI=8bsCcQ+*YaCIE`?n0g(97&$9 zaFAn6B>B%9cXhfwU7>!QQz6MUg>k4`sg6}t=bAv0NePs$CKOg?hx#EDR;MaUJ$9h| zB+SajSmiPs>*kHpWBn9tVRNkTY_7{296K=VH>~V9Z+LE}lv0x=$s%HpVs?vE*AajT zMWItn(b5QY80T0si$#eWwEsO` z833S&)PyWjzeVwT#9m1(kWtVyIl-x0P(qbaGT@Y?1et3L<)nEWIP7R|~`WIiDej5{_jBMv{OpZziqur{V2VBewfa1NiG^sN~xp-LIs9P5K4!;Bmq= sFR-f1EKy7Djjdo4_Rs5mB0jwyBx=3@Q3JmHeLr@-k^`0ybhrQj0OC@}3jhEB literal 0 HcmV?d00001 diff --git a/src/main/resources/static/img/payment-item.png.webp b/src/main/resources/static/img/payment-item.png.webp new file mode 100644 index 0000000000000000000000000000000000000000..04491b03b5776f311c1e5d41256196c5d480c6ee GIT binary patch literal 2836 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 mDzV}@q;af9Mh6vHHqd_2;%s5U3;+NrUYDW( literal 0 HcmV?d00001 diff --git a/src/main/resources/static/js/home.js b/src/main/resources/static/js/home.js new file mode 100644 index 0000000..8a71c4a --- /dev/null +++ b/src/main/resources/static/js/home.js @@ -0,0 +1,97 @@ +function getAllProductByCategory(categoryName, pageNumber) { + $.ajax({ + type: "GET", + url: "/getProductListByCategory/" + categoryName + "/" + pageNumber, + success: function (data) { + var productHTML = ""; + data.forEach(function (product) { + productHTML += "

"; + productHTML += ""; + productHTML += "
"; + productHTML += "" + product.name + ""; + productHTML += "
" + "$" + product.cost + ".00
"; + productHTML += "
"; + productHTML += "
"; + }); + $('.feature-product-item-wrapper').html(productHTML); + } + }); +} + +$(document).ready(function(){ + getAllProduct(0); // This will run the getAllProduct function when the document is fully loaded + getTotalPagesAll(); +}); + +function getAllProduct(pageNumber) { + $.ajax({ + type: "GET", + url: "/getProductList/" + pageNumber, + success: function (data) { + var productHTML = ""; + data.forEach(function (product) { + productHTML += "
"; + productHTML += ""; + productHTML += "
"; + productHTML += "" + product.name + ""; + productHTML += "
" + "$" + product.cost + ".00
"; + productHTML += "
"; + productHTML += "
"; + }); + $('.feature-product-item-wrapper').html(productHTML); + } + }); +} + +function pageChoose(val) { + console.log(val); + $('.pagination.pagination-sm .page-item').removeClass('active'); // Xóa class active ở tất cả các nút paging + var newElement = $('.pagination.pagination-sm .product-page').eq(val - 1).addClass('active'); + // newElement.addClass('active'); // Thêm class active cho nút paging được chọn +} + +function getTotalPagesAll() { + $.ajax({ + type: "GET", + url: "/getTotalPagesAll", + success: function (data) { + var pageHTML = ""; + for(var i = 1; i <= data; i++) { + if(i == 1) + pageHTML += "
  • "; + else + pageHTML += "
  • "; + pageHTML += "" + i + ""; + pageHTML += "
  • "; + } + $('.pagination.pagination-sm').html(pageHTML); + } + }); +} + + +function getTotalPages(categoryName) { + $.ajax({ + type: "GET", + url: "/getTotalPages/" + categoryName, + success: function (data) { + var pageHTML = ""; + for(var i = 1; i <= data; i++) { + if(i == 1) + pageHTML += "
  • "; + else + pageHTML += "
  • "; + pageHTML += "" + i + ""; + pageHTML += "
  • "; + } + $('.pagination.pagination-sm').html(pageHTML); + } + }); +} + + + + + diff --git a/src/main/resources/templates/fragments/footer.html b/src/main/resources/templates/fragments/footer.html new file mode 100644 index 0000000..3274b2f --- /dev/null +++ b/src/main/resources/templates/fragments/footer.html @@ -0,0 +1,54 @@ + + + + + Title + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/fragments/fragments.html b/src/main/resources/templates/fragments/fragments.html new file mode 100644 index 0000000..b55b1bc --- /dev/null +++ b/src/main/resources/templates/fragments/fragments.html @@ -0,0 +1,110 @@ + + + + + + + + + +
    +
    +
    +
    +
    +
      +
    • + + hello@colorlib.com +
    • +
    • Free Shipping for all Order of $99
    • +
    +
    +
    + +
    +
    + + +
    + +
    +
    +
    +
    + + \ No newline at end of file diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000..3efbff2 --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,216 @@ + + + + + Home + + + + + + + + + + + +
    +
    +
    + +
    + +
    +
    + + + +
    + +84 938601892 + support 24/7 time +
    +
    +
    + +
    +
    + +
    + + +
    +
    + + +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + +
    +
    + +
    +
    + + +
    + +
    +
    + +
    + +
    +
    + +

    +
    +
    +
    + +
    + + +
    + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html new file mode 100644 index 0000000..e4ba4a8 --- /dev/null +++ b/src/main/resources/templates/login.html @@ -0,0 +1,44 @@ + + + + + Login + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/register.html b/src/main/resources/templates/register.html new file mode 100644 index 0000000..e486aa0 --- /dev/null +++ b/src/main/resources/templates/register.html @@ -0,0 +1,61 @@ + + + + + Title + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplicationTests.java b/src/test/java/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplicationTests.java new file mode 100644 index 0000000..f35e8ee --- /dev/null +++ b/src/test/java/org/ecommerce/spring/boot/vegetable/project/VegetableProjectApplicationTests.java @@ -0,0 +1,13 @@ +package org.ecommerce.spring.boot.vegetable.project; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class VegetableProjectApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/src/test/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepositoryTest.java b/src/test/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepositoryTest.java new file mode 100644 index 0000000..5a5d0ef --- /dev/null +++ b/src/test/java/org/ecommerce/spring/boot/vegetable/project/repository/VerificationTokenRepositoryTest.java @@ -0,0 +1,20 @@ +package org.ecommerce.spring.boot.vegetable.project.repository; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class VerificationTokenRepositoryTest { + + @Autowired + private VerificationTokenRepository verificationTokenRepository; + + @Test + public void deleteVerificationTokenTest() { + verificationTokenRepository.deleteById(2L); + } + +} \ No newline at end of file