diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..e5df59fd40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +logs/ + +### 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/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000000..e76d1f3241 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed 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. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000..2cc7d4a55c Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000..ffdc10e59f --- /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.8.1/apache-maven-3.8.1-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/mvnw b/mvnw new file mode 100755 index 0000000000..a16b5431b4 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000000..c8d43372c9 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..90b11895a4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.5.1 + + + org.prgrms + kdt + 0.0.1-SNAPSHOT + kdt + Demo project for Spring Boot + + 16 + + + + org.springframework.boot + spring-boot-starter + + + + com.thoughtworks.xstream + xstream + 1.4.17 + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.springframework + spring-oxm + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-jdbc + + + + mysql + mysql-connector-java + runtime + + + + com.wix + wix-embedded-mysql + 4.6.1 + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/org/prgrms/kdt/AppConfiguration.java b/src/main/java/org/prgrms/kdt/AppConfiguration.java new file mode 100644 index 0000000000..26ffa58a97 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/AppConfiguration.java @@ -0,0 +1,23 @@ +package org.prgrms.kdt; + + +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +import javax.sql.DataSource; + +@Configuration +@ComponentScan( + basePackages = {"org.prgrms.kdt.voucher", "org.prgrms.kdt.order", "org.prgrms.kdt.io"} +) +@PropertySource(value = "application.yaml") +@EnableConfigurationProperties +public class AppConfiguration { + + +} diff --git a/src/main/java/org/prgrms/kdt/KdtApplication.java b/src/main/java/org/prgrms/kdt/KdtApplication.java new file mode 100644 index 0000000000..6d36d76a7f --- /dev/null +++ b/src/main/java/org/prgrms/kdt/KdtApplication.java @@ -0,0 +1,35 @@ +package org.prgrms.kdt; + +import org.prgrms.kdt.io.ModeController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +import java.io.IOException; + +@SpringBootApplication +public class KdtApplication { + + private final ModeController modeController; + + @Autowired + public KdtApplication(ModeController modeController) { + this.modeController = modeController; + } + + public static void main(String[] args) throws IOException { + ConfigurableApplicationContext applicationContext = SpringApplication.run(KdtApplication.class, args); + + KdtApplication kdtApplication = applicationContext.getBean(KdtApplication.class); + kdtApplication.run(); + } + + public void run() throws IOException { + while (true) { + if (!modeController.modeStart()) { + break; + } + } + } +} diff --git a/src/main/java/org/prgrms/kdt/customer/Customer.java b/src/main/java/org/prgrms/kdt/customer/Customer.java new file mode 100644 index 0000000000..32c7063d13 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/customer/Customer.java @@ -0,0 +1,65 @@ +package org.prgrms.kdt.customer; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class Customer { + + private final UUID customerId; + private String name; + private final String email; + private LocalDateTime lastLoginAt; + private final LocalDateTime createdAt; + + public Customer(UUID customerId, String name, String email, LocalDateTime createdAt) { + validateName(name); + this.name = name; + this.customerId = customerId; + this.email = email; + this.createdAt = createdAt; + } + + private void validateName(String name) { + if (name.isBlank()) { + throw new RuntimeException("Name should not be blank"); + } + } + + public Customer(UUID customerId, String name, String email, LocalDateTime lastLoginAt, LocalDateTime createdAt) { + validateName(name); + this.customerId = customerId; + this.name = name; + this.email = email; + this.lastLoginAt = lastLoginAt; + this.createdAt = createdAt; + } + + public void changeName(String name) { + validateName(name); + this.name = name; + } + + public void login() { + this.lastLoginAt = LocalDateTime.now(); + } + + public UUID getCustomerId() { + return customerId; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public LocalDateTime getLastLoginAt() { + return lastLoginAt; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } +} diff --git a/src/main/java/org/prgrms/kdt/customer/CustomerMessage.java b/src/main/java/org/prgrms/kdt/customer/CustomerMessage.java new file mode 100644 index 0000000000..26d050f14a --- /dev/null +++ b/src/main/java/org/prgrms/kdt/customer/CustomerMessage.java @@ -0,0 +1,18 @@ +package org.prgrms.kdt.customer; + +public enum CustomerMessage { + EXCEPTION_NOT_EXIST_CUSTOMER("존재하지 않는 회원입니다."), + GET_HAVE_VOUCHERS("가지고있는 바우처를 모두 조회할 고객의 아이디를 입력해주세요 : "), + CREATE_CUSTOMER_NAME("생성할 회원의 이름을 입력해주세요 : "), + CREATE_CUSTOMER_EMAIL("생성할 회원의 이메일을 입력해주세요 : "); + + private final String message; + + public String getMessage() { + return message; + } + + CustomerMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/org/prgrms/kdt/customer/CustomerService.java b/src/main/java/org/prgrms/kdt/customer/CustomerService.java new file mode 100644 index 0000000000..886a264534 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/customer/CustomerService.java @@ -0,0 +1,34 @@ +package org.prgrms.kdt.customer; + + +import org.prgrms.kdt.customer.repository.CustomerRepository; +import org.prgrms.kdt.wallet.Wallet; +import org.prgrms.kdt.wallet.WalletRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +public class CustomerService { + + private final CustomerRepository customerRepository; + private final WalletRepository walletRepository; + + public CustomerService(CustomerRepository customerRepository, WalletRepository walletRepository) { + this.customerRepository = customerRepository; + this.walletRepository = walletRepository; + } + + public Customer createCustomer(Customer customer) { + return customerRepository.save(customer); + } + + public List getBlackList() { + return customerRepository.findBlackList(); + } + + public List getVouchersById(UUID customerId) { + return walletRepository.findByCustomerId(customerId.toString()); + } +} diff --git a/src/main/java/org/prgrms/kdt/customer/controller/CustomerConsoleController.java b/src/main/java/org/prgrms/kdt/customer/controller/CustomerConsoleController.java new file mode 100644 index 0000000000..380b00b66a --- /dev/null +++ b/src/main/java/org/prgrms/kdt/customer/controller/CustomerConsoleController.java @@ -0,0 +1,98 @@ +package org.prgrms.kdt.customer.controller; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.customer.CustomerService; +import org.prgrms.kdt.io.InputHandler; +import org.prgrms.kdt.io.OutputHandler; +import org.prgrms.kdt.wallet.Wallet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.prgrms.kdt.customer.CustomerMessage.*; +import static org.prgrms.kdt.io.SystemMessage.EXCEPTION_NOT_EXIST_MENU; + +@Controller +public class CustomerConsoleController { + + private final CustomerService customerService; + private final OutputHandler outputHandler; + private final InputHandler inputHandler; + + private static final Logger logger = LoggerFactory.getLogger(CustomerConsoleController.class); + private static final String lineSeparator = System.lineSeparator(); + + private final String CREATE = "create"; + private final String VOUCHERS = "vouchers"; + private final String BLACK = "black"; + + public CustomerConsoleController(CustomerService customerService, OutputHandler outputHandler, InputHandler inputHandler) { + this.customerService = customerService; + this.outputHandler = outputHandler; + this.inputHandler = inputHandler; + } + + public void customerMenu() throws IOException { + String menu = selectCustomerMenu(); + + switch (menu) { + case CREATE: + createCustomer(); + break; + case VOUCHERS: + getHaveVouchers(); + break; + case BLACK: + getBlackList(); + break; + default: + String errorMessage = EXCEPTION_NOT_EXIST_MENU.getMessage(); + logger.error(errorMessage); + outputHandler.outputString(errorMessage); + break; + } + } + + private String selectCustomerMenu() throws IOException { + StringBuilder sb = new StringBuilder(); + sb.append("=== Customer Program ==="); + sb.append(lineSeparator); + sb.append("[1] Type 'create' to create a new customer."); + sb.append(lineSeparator); + sb.append("[2] Type 'vouchers' to view vouchers held by a specific customer."); + sb.append(lineSeparator); + sb.append("[3] Type 'black' to view the blacklisted list."); + sb.append(lineSeparator); + + outputHandler.outputString(sb.toString()); + + return inputHandler.inputString(); + } + + private void createCustomer() throws IOException { + outputHandler.outputString(CREATE_CUSTOMER_NAME.getMessage()); + String name = inputHandler.inputString(); + outputHandler.outputString(CREATE_CUSTOMER_EMAIL.getMessage()); + String email = inputHandler.inputString(); + + Customer newCustomer = new Customer(UUID.randomUUID(), name, email, LocalDateTime.now()); + customerService.createCustomer(newCustomer); + } + + private void getHaveVouchers() throws IOException { + outputHandler.outputString(GET_HAVE_VOUCHERS.getMessage()); + UUID customerId = UUID.fromString(inputHandler.inputString()); + List walletList = customerService.getVouchersById(customerId); + outputHandler.outputWallets(walletList); + } + + private void getBlackList() { + List blackList = customerService.getBlackList(); + outputHandler.outputBlackList(blackList); + } +} diff --git a/src/main/java/org/prgrms/kdt/customer/repository/CustomerJdbcRepository.java b/src/main/java/org/prgrms/kdt/customer/repository/CustomerJdbcRepository.java new file mode 100644 index 0000000000..b77e6f42db --- /dev/null +++ b/src/main/java/org/prgrms/kdt/customer/repository/CustomerJdbcRepository.java @@ -0,0 +1,189 @@ +package org.prgrms.kdt.customer.repository; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.domain.PercentDiscountVoucher; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.prgrms.kdt.voucher.VoucherMessage.EXCEPTION_VOUCHER_ROW_MAPPER; + +@Repository +@Profile("dev") +public class CustomerJdbcRepository implements CustomerRepository { + + private static final Logger logger = LoggerFactory.getLogger(CustomerJdbcRepository.class); + private final DataSource dataSource; + private final JdbcTemplate jdbcTemplate; + private final String FILE_PATH = "src/main/resources/customer_blacklist.csv"; + + private static final RowMapper customerRowMapper = (resultSet, i) -> { + var customerName = resultSet.getString("name"); + var email = resultSet.getString("email"); + var customerId = toUUID(resultSet.getBytes("customer_id")); + var lastLoginAt = resultSet.getTimestamp("last_login_at") != null ? + resultSet.getTimestamp("last_login_at").toLocalDateTime() : null; + var createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + return new Customer(customerId, customerName, email, lastLoginAt, createdAt); + }; + private static final RowMapper voucherRowMapper = (resultSet, i) -> { + UUID voucherId = toUUID(resultSet.getBytes("voucher_id")); + Integer amount = resultSet.getInt("amount"); + Integer percent = resultSet.getInt("percent"); + byte[] customerIdBytes = resultSet.getBytes("customer_id"); + var createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + UUID customerId = null; + if (customerIdBytes != null) { + customerId = toUUID(customerIdBytes); + } + + if (percent != 0) { + return new PercentDiscountVoucher(voucherId, percent, createdAt, customerId); + } + if (amount != 0) { + return new FixedAmountVoucher(voucherId, amount, createdAt, customerId); + } + + logger.error("JdbcVoucherRepository RowMapper Error"); + throw new RuntimeException(EXCEPTION_VOUCHER_ROW_MAPPER.getMessage()); + }; + public CustomerJdbcRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) { + this.dataSource = dataSource; + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Customer save(Customer customer) { + int update = jdbcTemplate.update("INSERT INTO customers(customer_id, name, email, created_at) VALUES (UUID_TO_BIN(?), ?, ?, ?)", + customer.getCustomerId().toString().getBytes(), + customer.getName(), + customer.getEmail(), + Timestamp.valueOf(customer.getCreatedAt())); + if (update != 1) { + throw new RuntimeException("Noting was inserted"); + } + return customer; + } + + @Override + public Customer update(Customer customer) { + var update = jdbcTemplate.update("UPDATE customers SET name = ?, email = ?, last_login_at = ? WHERE customer_id = UUID_TO_BIN(?)", + customer.getName(), + customer.getEmail(), + customer.getLastLoginAt() != null ? Timestamp.valueOf(customer.getLastLoginAt()) : null, + customer.getCustomerId().toString().getBytes() + ); + if (update != 1) { + throw new RuntimeException("Noting was updated"); + } + return customer; + } + + @Override + public int count() { + return jdbcTemplate.queryForObject("select count(*) from customers", Integer.class); + } + + @Override + public List findAll() { + return jdbcTemplate.query("select * from customers", customerRowMapper); + } + + @Override + public Optional findById(UUID customerId) { + try { + return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers WHERE customer_id = UUID_TO_BIN(?)", + customerRowMapper, + customerId.toString().getBytes())); + } catch (EmptyResultDataAccessException e) { + logger.error("Got empty result", e); + return Optional.empty(); + } + } + + @Override + public Optional findByName(String name) { + try { + return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers WHERE name = ?", + customerRowMapper, + name)); + } catch (EmptyResultDataAccessException e) { + logger.error("Got empty result", e); + return Optional.empty(); + } + } + + @Override + public Optional findByEmail(String email) { + try { + return Optional.ofNullable(jdbcTemplate.queryForObject("select * from customers WHERE email = ?", + customerRowMapper, + email)); + } catch (EmptyResultDataAccessException e) { + logger.error("Got empty result", e); + return Optional.empty(); + } + } + + @Override + public void deleteAll() { + jdbcTemplate.update("DELETE FROM customers"); + } + + @Override + public List findHaveVouchersById(UUID customerId) { + return jdbcTemplate.query("select * from wallets WHERE customer_id = UUID_TO_BIN(?)", + voucherRowMapper, + customerId.toString().getBytes()); + } + + @Override + public List findBlackList() { + List customers = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) { + String line; + + while ((line = reader.readLine()) != null) { + String[] data = line.split(","); + + if (data.length == 5) { + UUID id = UUID.fromString(data[0]); + String name = data[1]; + String email = data[2]; + LocalDateTime joinedAt = LocalDateTime.parse(data[3]); + + Customer customer = new Customer(id, name, email, joinedAt); + customers.add(customer); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + return customers; + } + + static UUID toUUID(byte[] bytes) { + var byteBuffer = ByteBuffer.wrap(bytes); + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); + } +} diff --git a/src/main/java/org/prgrms/kdt/customer/repository/CustomerRepository.java b/src/main/java/org/prgrms/kdt/customer/repository/CustomerRepository.java new file mode 100644 index 0000000000..e4ab03f0dd --- /dev/null +++ b/src/main/java/org/prgrms/kdt/customer/repository/CustomerRepository.java @@ -0,0 +1,31 @@ +package org.prgrms.kdt.customer.repository; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.voucher.domain.Voucher; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface CustomerRepository { + + List findHaveVouchersById(UUID customerId); + + List findBlackList(); + + Customer save(Customer customer); + + Customer update(Customer customer); + + int count(); + + List findAll(); + + Optional findById(UUID customerId); + + Optional findByName(String name); + + Optional findByEmail(String email); + + void deleteAll(); +} diff --git a/src/main/java/org/prgrms/kdt/io/ConsoleInputHandler.java b/src/main/java/org/prgrms/kdt/io/ConsoleInputHandler.java new file mode 100644 index 0000000000..a2ecfd0db4 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/ConsoleInputHandler.java @@ -0,0 +1,34 @@ +package org.prgrms.kdt.io; + +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import static org.prgrms.kdt.io.SystemMessage.EXCEPTION_INPUT; + +@Component +public class ConsoleInputHandler implements InputHandler{ + + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + + @Override + public String inputString() { + try { + return br.readLine(); + } catch (IOException e) { + throw new RuntimeException(EXCEPTION_INPUT.getMessage()); + } + } + + @Override + public int inputInt() { + try { + return Integer.parseInt(br.readLine()); + } catch (IOException e) { + throw new RuntimeException(EXCEPTION_INPUT.getMessage()); + } + } + +} diff --git a/src/main/java/org/prgrms/kdt/io/ConsoleOutputHandler.java b/src/main/java/org/prgrms/kdt/io/ConsoleOutputHandler.java new file mode 100644 index 0000000000..734969f85f --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/ConsoleOutputHandler.java @@ -0,0 +1,71 @@ +package org.prgrms.kdt.io; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.wallet.Wallet; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; + +@Component +public class ConsoleOutputHandler implements OutputHandler{ + + private static final String lineSeparator = System.lineSeparator(); + + @Override + public void outputString(String string) { + System.out.print(string + lineSeparator); + } + + @Override + public void outputCustomer(Customer customer) { + System.out.println(customer); + } + + @Override + public void outputVoucher(Voucher voucher) { + System.out.println(voucher); + } + + @Override + public void outputWallet(Optional wallet) { + System.out.println(wallet); + } + + @Override + public void outputWallets(List walletList) { + StringBuilder sb = new StringBuilder(); + + walletList.forEach(wallet -> { + sb.append(wallet); + sb.append(lineSeparator); + }); + + System.out.println(sb.toString()); + } + + @Override + public void outputVouchers(List voucherList) { + StringBuilder sb = new StringBuilder(); + + voucherList.forEach(voucher -> { + sb.append(voucher); + sb.append(lineSeparator); + }); + + System.out.println(sb.toString()); + } + + @Override + public void outputBlackList(List customerList) { + StringBuilder sb = new StringBuilder(); + + customerList.forEach(customer -> { + sb.append(customer); + sb.append(lineSeparator); + }); + + System.out.println(sb.toString()); + } +} diff --git a/src/main/java/org/prgrms/kdt/io/InputHandler.java b/src/main/java/org/prgrms/kdt/io/InputHandler.java new file mode 100644 index 0000000000..47cc479ba3 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/InputHandler.java @@ -0,0 +1,10 @@ +package org.prgrms.kdt.io; + +import java.io.IOException; + +public interface InputHandler { + + String inputString() throws IOException; + + int inputInt() throws IOException; +} diff --git a/src/main/java/org/prgrms/kdt/io/Mode.java b/src/main/java/org/prgrms/kdt/io/Mode.java new file mode 100644 index 0000000000..c0b0f78448 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/Mode.java @@ -0,0 +1,32 @@ +package org.prgrms.kdt.io; + +public enum Mode { + MODE_WALLET("wallet"), + MODE_CUSTOMER("customer"), + MODE_VOUCHER("voucher"), + MODE_EXIT("exit"), + MODE_MENU("=== Program ===" + System.lineSeparator() + + "[1] Type 'customer' to enter a customer menu." + System.lineSeparator() + + "[2] Type 'voucher' to enter a voucher menu." + System.lineSeparator() + + "[3] Type 'wallet' to enter a wallet menu." + System.lineSeparator() + + "[4] Type 'exit' to exit the program." + System.lineSeparator()); + + private final String message; + + Mode(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public static Mode fromString(String text) { + for (Mode mode : Mode.values()) { + if (mode.message.equalsIgnoreCase(text)) { + return mode; + } + } + return null; // You may handle null as per your requirement + } +} diff --git a/src/main/java/org/prgrms/kdt/io/ModeController.java b/src/main/java/org/prgrms/kdt/io/ModeController.java new file mode 100644 index 0000000000..c3ad7cf66c --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/ModeController.java @@ -0,0 +1,68 @@ +package org.prgrms.kdt.io; + +import org.prgrms.kdt.customer.controller.CustomerConsoleController; +import org.prgrms.kdt.voucher.controller.VoucherConsoleController; +import org.prgrms.kdt.wallet.WalletController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +import java.io.IOException; + +import static org.prgrms.kdt.io.Mode.MODE_MENU; +import static org.prgrms.kdt.io.SystemMessage.EXCEPTION_NOT_EXIST_MENU; +import static org.prgrms.kdt.io.SystemMessage.EXIT_PROGRAM; + +@Controller +public class ModeController { + + private final CustomerConsoleController customerConsoleController; + private final VoucherConsoleController voucherConsoleController; + private final WalletController walletController; + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + + private static final Logger logger = LoggerFactory.getLogger(ModeController.class); + + public ModeController(CustomerConsoleController customerConsoleController, VoucherConsoleController voucherConsoleController, + WalletController walletController, InputHandler inputHandler, OutputHandler outputHandler) { + this.customerConsoleController = customerConsoleController; + this.voucherConsoleController = voucherConsoleController; + this.walletController = walletController; + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + } + + public boolean modeStart() throws IOException { + String userInput = modeSelect(); + Mode menu = Mode.fromString(userInput); + + switch (menu) { + case MODE_CUSTOMER: + customerConsoleController.customerMenu(); + break; + case MODE_VOUCHER: + voucherConsoleController.voucherMenu(); + break; + case MODE_WALLET: + walletController.walletMenu(); + break; + case MODE_EXIT: + outputHandler.outputString(EXIT_PROGRAM.getMessage()); + System.exit(0); + break; + default: + String errorMessage = EXCEPTION_NOT_EXIST_MENU.getMessage(); + logger.error(errorMessage); + outputHandler.outputString(errorMessage); + break; + } + + return true; // 프로그램 유지 + } + + private String modeSelect() throws IOException { + outputHandler.outputString(MODE_MENU.getMessage()); + return inputHandler.inputString(); + } +} diff --git a/src/main/java/org/prgrms/kdt/io/OutputHandler.java b/src/main/java/org/prgrms/kdt/io/OutputHandler.java new file mode 100644 index 0000000000..0810de5d84 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/OutputHandler.java @@ -0,0 +1,25 @@ +package org.prgrms.kdt.io; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.wallet.Wallet; + +import java.util.List; +import java.util.Optional; + +public interface OutputHandler { + + void outputString(String message); + + void outputCustomer(Customer customer); + + void outputVoucher(Voucher voucher); + + void outputWallet(Optional wallet); + + void outputWallets(List walletList); + + void outputVouchers(List voucherList); + + void outputBlackList(List customerList); +} diff --git a/src/main/java/org/prgrms/kdt/io/SystemMessage.java b/src/main/java/org/prgrms/kdt/io/SystemMessage.java new file mode 100644 index 0000000000..b18b9fcbcb --- /dev/null +++ b/src/main/java/org/prgrms/kdt/io/SystemMessage.java @@ -0,0 +1,17 @@ +package org.prgrms.kdt.io; + +public enum SystemMessage { + EXIT_PROGRAM("프로그램을 종료합니다."), + EXCEPTION_INPUT("입력값이 잘못되었습니다."), + EXCEPTION_NOT_EXIST_MENU("올바른 메뉴를 입력해주세요."); + + private final String message; + + public String getMessage() { + return message; + } + + SystemMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/org/prgrms/kdt/order/MemoryOrderRepository.java b/src/main/java/org/prgrms/kdt/order/MemoryOrderRepository.java new file mode 100644 index 0000000000..fd60561740 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/MemoryOrderRepository.java @@ -0,0 +1,18 @@ +package org.prgrms.kdt.order; + +import org.springframework.stereotype.Repository; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +public class MemoryOrderRepository implements OrderRepository { + private final Map storage = new ConcurrentHashMap<>(); + + @Override + public Order insert(Order order) { + storage.put(order.getOrderId(), order); + return order; + } +} diff --git a/src/main/java/org/prgrms/kdt/order/Order.java b/src/main/java/org/prgrms/kdt/order/Order.java new file mode 100644 index 0000000000..3563f7ae43 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/Order.java @@ -0,0 +1,61 @@ +package org.prgrms.kdt.order; + + +import org.prgrms.kdt.voucher.domain.Voucher; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class Order { + private final UUID orderId; + private final UUID customerId; + private final List orderItems; + private Optional voucher; + private OrderStatus orderStatus = OrderStatus.ACCEPTED; + + public Order(UUID orderid, UUID customerId, List orderItems) { + this.orderId = orderid; + this.customerId = customerId; + this.orderItems = orderItems; + this.voucher = Optional.empty(); + } + + public Order(UUID orderid, UUID customerId, List orderItems, Voucher voucher) { + this.orderId = orderid; + this.customerId = customerId; + this.orderItems = orderItems; + this.voucher = Optional.of(voucher); + } + + public long totalAmount() { + var beforeDiscount = orderItems.stream() + .map(v -> v.getProductPrice() * v.getQuantity()) + .reduce(0L, Long::sum); + return voucher.map(value -> value.discount(beforeDiscount)).orElse(beforeDiscount); + } + + public void setOrderStatus(OrderStatus orderStatus) { + this.orderStatus = orderStatus; + } + + public UUID getOrderId() { + return orderId; + } + + public Optional getVoucher() { + return voucher; + } + + public OrderStatus getOrderStatus() { + return orderStatus; + } + + public List getOrderItems() { + return orderItems; + } + + public UUID getCustomerId() { + return customerId; + } +} diff --git a/src/main/java/org/prgrms/kdt/order/OrderItem.java b/src/main/java/org/prgrms/kdt/order/OrderItem.java new file mode 100644 index 0000000000..0c8caf4cfe --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/OrderItem.java @@ -0,0 +1,28 @@ +package org.prgrms.kdt.order; + +import java.util.UUID; + +public class OrderItem { + public final UUID productId; + public final long productPrice; + public final long quantity; + + public OrderItem(UUID productId, long productPrice, int quantity) { + this.productId = productId; + this.productPrice = productPrice; + this.quantity = quantity; + } + + public UUID getProductId() { + return productId; + } + + public long getProductPrice() { + return productPrice; + } + + public long getQuantity() { + return quantity; + } +} + diff --git a/src/main/java/org/prgrms/kdt/order/OrderProperties.java b/src/main/java/org/prgrms/kdt/order/OrderProperties.java new file mode 100644 index 0000000000..6bf9aa5cca --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/OrderProperties.java @@ -0,0 +1,73 @@ +package org.prgrms.kdt.order; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@ConfigurationProperties(prefix = "kdt") +public class OrderProperties implements InitializingBean { + + private final static Logger logger = LoggerFactory.getLogger(OrderProperties.class); + + private String version; + + private int minimumOrderAmount; + + private List supportVendors; + + private String description; + + @Value("${JAVA_HOME}") + private String javaHome; + + @Override + public void afterPropertiesSet() throws Exception { + logger.debug("[OrderProperties] version -> {}", version); + logger.debug("[OrderProperties] minimumOrderAmount -> {}", minimumOrderAmount); + logger.debug("[OrderProperties] supportVendors -> {}", supportVendors); + logger.debug("[OrderProperties] javaHome -> {}", javaHome); + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public int getMinimumOrderAmount() { + return minimumOrderAmount; + } + + public void setMinimumOrderAmount(int minimumOrderAmount) { + this.minimumOrderAmount = minimumOrderAmount; + } + + public List getSupportVendors() { + return supportVendors; + } + + public void setSupportVendors(List supportVendors) { + this.supportVendors = supportVendors; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getJavaHome() { + return javaHome; + } + +} diff --git a/src/main/java/org/prgrms/kdt/order/OrderRepository.java b/src/main/java/org/prgrms/kdt/order/OrderRepository.java new file mode 100644 index 0000000000..9f7cf37028 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/OrderRepository.java @@ -0,0 +1,5 @@ +package org.prgrms.kdt.order; + +public interface OrderRepository { + Order insert(Order order); +} diff --git a/src/main/java/org/prgrms/kdt/order/OrderService.java b/src/main/java/org/prgrms/kdt/order/OrderService.java new file mode 100644 index 0000000000..66f04a5d15 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/OrderService.java @@ -0,0 +1,32 @@ +package org.prgrms.kdt.order; + +import org.prgrms.kdt.voucher.service.VoucherService; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +public class OrderService { + private final VoucherService voucherService; + private final OrderRepository orderRepository; + + public OrderService(VoucherService voucherService, OrderRepository orderRepository) { + this.voucherService = voucherService; + this.orderRepository = orderRepository; + } + + public Order createOrder(UUID customerId, List orderItems) { + var order = new Order(UUID.randomUUID(), customerId, orderItems); + return orderRepository.insert(order); + } + + public Order createOrder(UUID customerId, List orderItems, UUID voucherId) { + var voucher = voucherService.getVoucher(voucherId); + var order = new Order(UUID.randomUUID(), customerId, orderItems, voucher); + orderRepository.insert(order); + voucherService.removeVoucherById(voucher.getVoucherId()); + return order; + } + +} diff --git a/src/main/java/org/prgrms/kdt/order/OrderStatus.java b/src/main/java/org/prgrms/kdt/order/OrderStatus.java new file mode 100644 index 0000000000..85b984578a --- /dev/null +++ b/src/main/java/org/prgrms/kdt/order/OrderStatus.java @@ -0,0 +1,12 @@ +package org.prgrms.kdt.order; + +public enum OrderStatus { + ACCEPTED, + PAYMENT_REQUIRED, + PAYMENT_CONFIRMED, + PAYMENT_REJECTED, + READY_FOR_DELIVERY, + SHIPPED, + SETTLED, + CANCELLED +} diff --git a/src/main/java/org/prgrms/kdt/servlet/KdtWebApplicationInitializer.java b/src/main/java/org/prgrms/kdt/servlet/KdtWebApplicationInitializer.java new file mode 100644 index 0000000000..619dcb622a --- /dev/null +++ b/src/main/java/org/prgrms/kdt/servlet/KdtWebApplicationInitializer.java @@ -0,0 +1,124 @@ +package org.prgrms.kdt.servlet; + +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.zaxxer.hikari.HikariDataSource; +import org.prgrms.kdt.customer.controller.CustomerConsoleController; +import org.prgrms.kdt.voucher.controller.VoucherRestController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MarshallingHttpMessageConverter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.oxm.xstream.XStreamMarshaller; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.springframework.web.WebApplicationInitializer; +import org.springframework.web.context.ContextLoaderListener; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.config.annotation.*; +import org.springframework.web.servlet.resource.EncodedResourceResolver; +import org.thymeleaf.spring5.SpringTemplateEngine; +import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; +import org.thymeleaf.spring5.view.ThymeleafViewResolver; + +import javax.servlet.ServletContext; +import javax.sql.DataSource; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +public class KdtWebApplicationInitializer implements WebApplicationInitializer { + private static final Logger logger = LoggerFactory.getLogger(KdtWebApplicationInitializer.class); + + @Configuration + @EnableWebMvc + @ComponentScan(basePackages = "org.prgrms.kdt", + includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = VoucherRestController.class), + useDefaultFilters = false + ) + static class ServletConfig implements WebMvcConfigurer, ApplicationContextAware { + + ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void extendMessageConverters(List> converters) { + var messageConverter = new MarshallingHttpMessageConverter(); + var xStreamMarshaller = new XStreamMarshaller(); + messageConverter.setMarshaller(xStreamMarshaller); + messageConverter.setUnmarshaller(xStreamMarshaller); + converters.add(0, messageConverter); + + var javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_DATE_TIME)); + var modules = Jackson2ObjectMapperBuilder.json().modules(javaTimeModule); + converters.add(1, new MappingJackson2HttpMessageConverter(modules.build())); + } + } + + + + @Configuration + @ComponentScan(basePackages = "org.prgrms.kdt", + excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = CustomerConsoleController.class) + ) + @EnableTransactionManagement + static class RootConfig { + + @Bean + public DataSource dataSource() { + return DataSourceBuilder.create() + .url("jdbc:mysql://localhost:3306/order_mgmt") + .username("root") + .password("110811") + .type(HikariDataSource.class) + .build(); + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + @Bean + public PlatformTransactionManager platformTransactionManager(DataSource dataSource) { + return new DataSourceTransactionManager(dataSource); + } + + } + + @Override + public void onStartup(ServletContext servletContext) { + logger.info("Staring Server..."); + var rootApplicationContext = new AnnotationConfigWebApplicationContext(); + rootApplicationContext.register(RootConfig.class); + var loaderListener = new ContextLoaderListener(rootApplicationContext); + servletContext.addListener(loaderListener); + + var applicationContext = new AnnotationConfigWebApplicationContext(); + applicationContext.register(ServletConfig.class); + var dispatcherServlet = new DispatcherServlet(applicationContext); + var servletRegistration = servletContext.addServlet("test", dispatcherServlet); + servletRegistration.addMapping("/"); + servletRegistration.setLoadOnStartup(1); + } + +} diff --git a/src/main/java/org/prgrms/kdt/voucher/VoucherMessage.java b/src/main/java/org/prgrms/kdt/voucher/VoucherMessage.java new file mode 100644 index 0000000000..0802c2b099 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/VoucherMessage.java @@ -0,0 +1,36 @@ +package org.prgrms.kdt.voucher; + +public enum VoucherMessage { + SELECT_VOUCHER_MENU("=== Voucher Program ===" + System.lineSeparator() + + "[1] Type 'create' to create a new voucher." + System.lineSeparator() + + "[2] Type 'owner' to view customer with a specific voucher." + System.lineSeparator() + + "[3] Type 'remove' to remove a specific voucher." + System.lineSeparator() + + "[4] Type 'find' to find all vouchers." + System.lineSeparator() + + "[5] Type 'details' to view details of a specific voucher." + System.lineSeparator()), + CREATE_VOUCHER_TYPE("fixed, percent 중에서 만들 바우처의 타입을 입력해주세요 : "), + CREATE_FIXED_VOUCHER("고정으로 할인되는 비용을 입력해주세요 : "), + CREATE_PERCENT_VOUCHER("할인되는 퍼센트를 정수로 입력해주세요 : "), + INPUT_VOUCHER_ID("바우처의 아이디를 입력해주세요 : "), + VOUCHER_IS_EMPTY("조회한 바우처가 하나도 존재하지 않습니다."), + + EXCEPTION_VOUCHER_TYPE("올바른 바우처 타입을 입력하세요."), + EXCEPTION_VOUCHER_ID("올바른 바우처 아이디를 입력하세요."), + EXCEPTION_FIXED_AMOUNT_MINUS("할인되는 비용이 0보다 커야합니다."), + EXCEPTION_FIXED_AMOUNT_OVER("할인되는 비용이 너무 큽니다. 10만원 밑으로 설정해주세요."), + EXCEPTION_PERCENT_MINUS("할인되는 퍼센트가 0보다 커야합니다."), + EXCEPTION_PERCENT_OVER("할인되는 퍼센트가 100을 넘을 수 없습니다."), + EXCEPTION_FIND_VOUCHER(" -> 해당 id의 바우처를 찾을 수 없습니다."), + EXCEPTION_VOUCHER_ROW_MAPPER("바우처의 ROW MAPPER 가 실패했습니다."), + EXCEPTION_NOT_EXIST_VOUCHER("존재하지 않는 바우처입니다."); + + private final String message; + private static final String lineSeparator = System.lineSeparator(); + + public String getMessage() { + return message; + } + + VoucherMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/controller/VoucherConsoleController.java b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherConsoleController.java new file mode 100644 index 0000000000..a9490837d2 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherConsoleController.java @@ -0,0 +1,121 @@ +package org.prgrms.kdt.voucher.controller; + +import org.prgrms.kdt.io.InputHandler; +import org.prgrms.kdt.io.OutputHandler; +import org.prgrms.kdt.voucher.service.VoucherService; +import org.prgrms.kdt.wallet.Wallet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; + +import static org.prgrms.kdt.io.SystemMessage.EXCEPTION_NOT_EXIST_MENU; +import static org.prgrms.kdt.voucher.VoucherMessage.*; +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +@Controller +public class VoucherConsoleController { + + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + private final VoucherService voucherService; + private static final Logger logger = LoggerFactory.getLogger(VoucherConsoleController.class); + + public VoucherConsoleController(InputHandler inputHandler, OutputHandler outputHandler, VoucherService voucherService) { + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + this.voucherService = voucherService; + } + + public void voucherMenu() throws IOException { + String userInput = selectVoucherMenu(); + VoucherMenu menu = VoucherMenu.fromString(userInput); + + switch (menu) { + case FIND: + voucherList(); + break; + case DETAILS: + voucherDetails(); + break; + case CREATE: + voucherAdd(); + break; + case OWNER: + getOwner(); + break; + case REMOVE: + voucherRemove(); + break; + default: + String errorMessage = EXCEPTION_NOT_EXIST_MENU.getMessage(); + logger.error(errorMessage); + outputHandler.outputString(errorMessage); + break; + } + } + + private void voucherList() { + outputHandler.outputVouchers(voucherService.getAllVouchers()); + } + + private void voucherDetails() { + outputHandler.outputString(INPUT_VOUCHER_ID.getMessage()); + UUID voucherId = null; + try { + voucherId = UUID.fromString(inputHandler.inputString()); + } catch (IOException e) { + throw new RuntimeException(EXCEPTION_VOUCHER_ID.getMessage()); + } + outputHandler.outputVoucher(voucherService.getVoucher(voucherId)); + } + + private String selectVoucherMenu() throws IOException { + outputHandler.outputString(SELECT_VOUCHER_MENU.getMessage()); + return inputHandler.inputString().trim().toLowerCase(); // Lowercased to match ignore case + } + + private void getOwner() throws IOException { + outputHandler.outputString(INPUT_VOUCHER_ID.getMessage()); + UUID voucherId = UUID.fromString(inputHandler.inputString()); + Optional wallet = voucherService.getOwner(voucherId); + outputHandler.outputWallet(wallet); + } + + private void voucherAdd() throws IOException { + outputHandler.outputString(CREATE_VOUCHER_TYPE.getMessage()); + var createVoucherType = inputHandler.inputString(); + + if (createVoucherType.equals(FIXED.getType())) { + fixedAmountVoucherAdd(); + } else if (createVoucherType.equals(PERCENT.getType())) { + percentDiscountVoucherAdd(); + } else { + String errorMessage = EXCEPTION_VOUCHER_TYPE.getMessage(); + logger.error(errorMessage); + outputHandler.outputString(errorMessage); + } + } + + private void fixedAmountVoucherAdd() throws IOException { + outputHandler.outputString(CREATE_FIXED_VOUCHER.getMessage()); + var amount = inputHandler.inputInt(); + voucherService.createFixedAmountVoucher(amount); + } + + private void percentDiscountVoucherAdd() throws IOException { + outputHandler.outputString(CREATE_PERCENT_VOUCHER.getMessage()); + var amount = inputHandler.inputInt(); + voucherService.createPercentDiscountVoucher(amount); + } + + private void voucherRemove() throws IOException { + outputHandler.outputString(INPUT_VOUCHER_ID.getMessage()); + UUID voucherId = UUID.fromString(inputHandler.inputString()); + voucherService.removeVoucherById(voucherId); + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/controller/VoucherMenu.java b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherMenu.java new file mode 100644 index 0000000000..cac12b794e --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherMenu.java @@ -0,0 +1,28 @@ +package org.prgrms.kdt.voucher.controller; + +public enum VoucherMenu { + CREATE("create"), + OWNER("owner"), + FIND("find"), + DETAILS("details"), + REMOVE("remove"); + + private final String value; + + VoucherMenu(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static VoucherMenu fromString(String text) { + for (VoucherMenu menu : VoucherMenu.values()) { + if (menu.value.equalsIgnoreCase(text)) { + return menu; + } + } + return null; // You may handle null as per your requirement + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/controller/VoucherRestController.java b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherRestController.java new file mode 100644 index 0000000000..6772299cbf --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherRestController.java @@ -0,0 +1,68 @@ +package org.prgrms.kdt.voucher.controller; + +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.service.VoucherService; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping(value = "/api/v1/vouchers", produces = MediaType.APPLICATION_JSON_VALUE) // xml로 변경 가능 +public class VoucherRestController { + + private final VoucherService voucherService; + + public VoucherRestController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping("") + public ResponseEntity> getAllVouchers() { + List vouchers = voucherService.getAllVouchers(); + if (vouchers.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + return new ResponseEntity<>(vouchers, HttpStatus.OK); + } + + @PostMapping("/create") + public ResponseEntity createVoucher(@RequestParam String type, @RequestParam int amount) { + if (type.equals("fixed")) { + voucherService.createFixedAmountVoucher(amount); + return new ResponseEntity<>("Fixed amount voucher created successfully", HttpStatus.CREATED); + } else if (type.equals("percent")) { + voucherService.createPercentDiscountVoucher(amount); + return new ResponseEntity<>("Percent discount voucher created successfully", HttpStatus.CREATED); + } else { + return new ResponseEntity<>("Invalid voucher type", HttpStatus.BAD_REQUEST); + } + } + + @DeleteMapping("/{voucherId}") + public ResponseEntity removeVoucher(@PathVariable UUID voucherId) { + voucherService.removeVoucherById(voucherId); + return new ResponseEntity<>("Voucher removed successfully", HttpStatus.OK); + } + + @GetMapping("/{voucherId}") + public ResponseEntity getVoucherById(@PathVariable UUID voucherId) { + Voucher voucher = voucherService.getVoucher(voucherId); + return new ResponseEntity<>(voucher, HttpStatus.OK); + } + + @GetMapping("/criteria") + public ResponseEntity> getVouchersByCriteria(@RequestParam String startDate, @RequestParam String endDate, @RequestParam String voucherType) { + LocalDateTime start = LocalDateTime.parse(startDate); + LocalDateTime end = LocalDateTime.parse(endDate); + List vouchers = voucherService.getVouchersByCriteria(start, end, voucherType); + if (vouchers.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + return new ResponseEntity<>(vouchers, HttpStatus.OK); + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/controller/VoucherThymeleafController.java b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherThymeleafController.java new file mode 100644 index 0000000000..cbdf555962 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/controller/VoucherThymeleafController.java @@ -0,0 +1,62 @@ +package org.prgrms.kdt.voucher.controller; + +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.service.VoucherService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +@Controller +@RequestMapping("/vouchers") +public class VoucherThymeleafController { + + private final VoucherService voucherService; + private static final Logger logger = LoggerFactory.getLogger(VoucherThymeleafController.class); + + public VoucherThymeleafController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping("") + public String showVouchers(Model model) { + List vouchers = voucherService.getAllVouchers(); + model.addAttribute("vouchers", vouchers); + return "voucher/voucher-list"; + } + + @GetMapping("/create") + public String showCreateVoucherForm() { + return "voucher/voucher-create"; + } + + @PostMapping("/create") + public String createVoucher(@RequestParam("type") String voucherType, + @RequestParam(value = "amount", required = false) Integer amount, + @RequestParam(value = "percent", required = false) Integer percent + ) { + if (voucherType.equals(FIXED.getType())) { + voucherService.createFixedAmountVoucher(amount); + } else if (voucherType.equals(PERCENT.getType())) { + voucherService.createPercentDiscountVoucher(percent); + } else { + logger.error("Unknown voucher type: " + voucherType); + } + + return "redirect:/vouchers"; + } + + @PostMapping("/remove/{id}") + public String removeVoucher(@PathVariable("id") String voucherId) { + UUID id = UUID.fromString(voucherId); + voucherService.removeVoucherById(id); + return "redirect:/vouchers"; + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/domain/FixedAmountVoucher.java b/src/main/java/org/prgrms/kdt/voucher/domain/FixedAmountVoucher.java new file mode 100644 index 0000000000..b8a944489f --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/domain/FixedAmountVoucher.java @@ -0,0 +1,90 @@ +package org.prgrms.kdt.voucher.domain; + +import java.time.LocalDateTime; +import java.util.UUID; + +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; + +public class FixedAmountVoucher extends Voucher { + + private final String type = FIXED.getType(); + + public FixedAmountVoucher(UUID voucherId, long amount, LocalDateTime createdAt) { + this.voucherId = voucherId; + this.amount = amount; + this.createdAt = createdAt; + } + + public FixedAmountVoucher(UUID voucherId, long amount, LocalDateTime createdAt, UUID customerId) { + this.voucherId = voucherId; + this.amount = amount; + this.createdAt = createdAt; + this.customerId = customerId; + } + + public static FixedAmountVoucher fromDto(VoucherDto voucherDto) { + if (voucherDto == null) { + throw new IllegalArgumentException("Voucher DTO cannot be null"); + } + + if (voucherDto.getAmount() <= 0) { + throw new IllegalArgumentException("Amount should be greater than 0"); + } + + if (voucherDto.getAmount() >= 100_000) { + throw new IllegalArgumentException("Amount should be less than 100,000"); + } + + if (!FIXED.getType().equals(voucherDto.getType())) { + throw new IllegalArgumentException("Invalid voucher type for FixedAmountVoucher"); + } + + return new FixedAmountVoucher(voucherDto.getVoucherId(), voucherDto.getAmount(), voucherDto.getCreatedAt()); + } + + @Override + public long discount(long beforeDiscount) { + return beforeDiscount - amount; + } + + @Override + public UUID getVoucherId() { + return voucherId; + } + + @Override + public long getAmount() { + return amount; + } + + @Override + public UUID getCustomerId() { + return customerId; + } + + @Override + public void setCustomerId(UUID customerId) { + this.customerId = customerId; + } + + @Override + public String getType() { + return type; + } + + @Override + public LocalDateTime getCreatedAt() { + return createdAt; + } + + @Override + public String toString() { + return "FixedAmountVoucher{" + + "type='" + type + '\'' + + ", voucherId=" + voucherId + + ", amount=" + amount + + ", createdAt=" + createdAt + + ", customerId=" + customerId + + '}'; + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/domain/PercentDiscountVoucher.java b/src/main/java/org/prgrms/kdt/voucher/domain/PercentDiscountVoucher.java new file mode 100644 index 0000000000..3dde75c25b --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/domain/PercentDiscountVoucher.java @@ -0,0 +1,90 @@ +package org.prgrms.kdt.voucher.domain; + +import java.time.LocalDateTime; +import java.util.UUID; + +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +public class PercentDiscountVoucher extends Voucher { + + private final String type = PERCENT.getType(); + + public PercentDiscountVoucher(UUID voucherId, int amount, LocalDateTime createdAt) { + this.voucherId = voucherId; + this.amount = amount; + this.createdAt = createdAt; + } + + public PercentDiscountVoucher(UUID voucherId, int amount, LocalDateTime createdAt, UUID customerId) { + this.voucherId = voucherId; + this.amount = amount; + this.customerId = customerId; + this.createdAt = createdAt; + } + + public static PercentDiscountVoucher fromDto(VoucherDto voucherDto) { + if (voucherDto == null) { + throw new IllegalArgumentException("Voucher DTO cannot be null"); + } + + if (voucherDto.getAmount() <= 0) { + throw new IllegalArgumentException("Amount should be greater than 0"); + } + + if (voucherDto.getAmount() >= 100) { + throw new IllegalArgumentException("Amount should be less than 100"); + } + + if (!"percent".equals(voucherDto.getType())) { + throw new IllegalArgumentException("Invalid voucher type for PercentDiscountVoucher"); + } + + return new PercentDiscountVoucher(voucherDto.getVoucherId(), voucherDto.getAmount(), voucherDto.getCreatedAt()); + } + + @Override + public long discount(long beforeDiscount) { + return beforeDiscount * (amount / 100); + } + + @Override + public long getAmount() { + return amount; + } + + @Override + public UUID getVoucherId() { + return voucherId; + } + + @Override + public UUID getCustomerId() { + return null; + } + + @Override + public String getType() { + return type; + } + + @Override + public LocalDateTime getCreatedAt() { + return createdAt; + } + + @Override + public void setCustomerId(UUID customerId) { + this.customerId = customerId; + } + + @Override + public String toString() { + return "PercentDiscountVoucher{" + + "type='" + type + '\'' + + ", voucherId=" + voucherId + + ", amount=" + amount + + ", createdAt=" + createdAt + + ", customerId=" + customerId + + '}'; + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/domain/Voucher.java b/src/main/java/org/prgrms/kdt/voucher/domain/Voucher.java new file mode 100644 index 0000000000..2014123a7e --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/domain/Voucher.java @@ -0,0 +1,26 @@ +package org.prgrms.kdt.voucher.domain; + +import java.time.LocalDateTime; +import java.util.UUID; + +public abstract class Voucher { + + UUID voucherId; + long amount; + LocalDateTime createdAt; + UUID customerId = null; + + public abstract long discount(long beforeDiscount); + + public abstract UUID getVoucherId(); + + public abstract long getAmount(); + + public abstract UUID getCustomerId(); + + public abstract void setCustomerId(UUID customerId); + + public abstract String getType(); + + public abstract LocalDateTime getCreatedAt(); +} diff --git a/src/main/java/org/prgrms/kdt/voucher/domain/VoucherDto.java b/src/main/java/org/prgrms/kdt/voucher/domain/VoucherDto.java new file mode 100644 index 0000000000..612db0244c --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/domain/VoucherDto.java @@ -0,0 +1,47 @@ +package org.prgrms.kdt.voucher.domain; + +import java.time.LocalDateTime; +import java.util.UUID; + +public class VoucherDto { + private final UUID voucherId; + private final int amount; + private final LocalDateTime createdAt; + private final String type; + private UUID customerId; + + public VoucherDto(UUID voucherId, int amount, String type, LocalDateTime createdAt) { + this.voucherId = voucherId; + this.amount = amount; + this.type = type; + this.createdAt = createdAt; + } + + public VoucherDto(UUID voucherId, int amount, LocalDateTime createdAt, String type, UUID customerId) { + this.voucherId = voucherId; + this.amount = amount; + this.createdAt = createdAt; + this.type = type; + this.customerId = customerId; + } + + public UUID getVoucherId() { + return voucherId; + } + + public int getAmount() { + return amount; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public String getType() { + return type; + } + + public UUID getCustomerId() { + return customerId; + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/domain/VoucherType.java b/src/main/java/org/prgrms/kdt/voucher/domain/VoucherType.java new file mode 100644 index 0000000000..02203036ec --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/domain/VoucherType.java @@ -0,0 +1,16 @@ +package org.prgrms.kdt.voucher.domain; + +public enum VoucherType { + FIXED("fixed"), + PERCENT("percent"); + + VoucherType(String type) { + this.type = type; + } + + private final String type; + + public String getType() { + return type; + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/repository/VoucherJdbcRepository.java b/src/main/java/org/prgrms/kdt/voucher/repository/VoucherJdbcRepository.java new file mode 100644 index 0000000000..66803d5cbd --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/repository/VoucherJdbcRepository.java @@ -0,0 +1,121 @@ +package org.prgrms.kdt.voucher.repository; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.domain.PercentDiscountVoucher; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.domain.VoucherDto; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.nio.ByteBuffer; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +import static org.prgrms.kdt.voucher.VoucherMessage.EXCEPTION_VOUCHER_ROW_MAPPER; +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +@Repository +@Profile("dev") +public class VoucherJdbcRepository implements VoucherRepository { + + private final DataSource dataSource; + private final JdbcTemplate jdbcTemplate; + private static final Logger logger = LoggerFactory.getLogger(VoucherJdbcRepository.class); + private static final RowMapper voucherRowMapper = (resultSet, i) -> { + UUID voucherId = toUUID(resultSet.getBytes("voucher_id")); + String type = resultSet.getString("type"); + Integer amount = resultSet.getInt("amount"); + LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); + byte[] customerIdBytes = resultSet.getBytes("customer_id"); + UUID customerId = null; + if (customerIdBytes != null) { + customerId = toUUID(customerIdBytes); + } + + if (type.equals(FIXED.getType())) { + return new FixedAmountVoucher(voucherId, amount, createdAt, customerId); + } + if (type.equals(PERCENT.getType())) { + return new PercentDiscountVoucher(voucherId, amount, createdAt, customerId); + } + + logger.error("JdbcVoucherRepository RowMapper Error"); + throw new RuntimeException(EXCEPTION_VOUCHER_ROW_MAPPER.getMessage()); + }; + + public VoucherJdbcRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) { + this.dataSource = dataSource; + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Optional findById(UUID voucherId) { + try { + return Optional.ofNullable(jdbcTemplate.queryForObject("select * from vouchers WHERE voucher_id = UUID_TO_BIN(?)", + voucherRowMapper, + voucherId.toString().getBytes())); + } catch (EmptyResultDataAccessException e) { + logger.error("Got empty result", e); + return Optional.empty(); + } + } + + @Override + public Voucher save(VoucherDto voucherDto) { + Voucher voucher = null; + if(voucherDto.getType().equals(FIXED.getType())){ + voucher = FixedAmountVoucher.fromDto(voucherDto); + } + if(voucherDto.getType().equals(PERCENT.getType())){ + voucher = PercentDiscountVoucher.fromDto(voucherDto); + } + + int update = jdbcTemplate.update("INSERT INTO vouchers(voucher_id, type, amount, created_at, customer_id) " + + "VALUES (UUID_TO_BIN(?), ?, ?, ?, UUID_TO_BIN(?))", + voucher.getVoucherId().toString().getBytes(), + voucher.getType(), + voucher.getAmount(), + voucher.getCreatedAt(), + voucher.getCustomerId() != null ? voucher.getCustomerId().toString().getBytes() : null + ); + + if (update != 1) { + throw new RuntimeException("Noting was inserted"); + } + return voucher; + } + + @Override + public List findAll() { + return jdbcTemplate.query("select * from vouchers", voucherRowMapper); + } + + @Override + public void deleteById(UUID voucherId) { + int deletedRows = jdbcTemplate.update("DELETE FROM vouchers WHERE voucher_id = UUID_TO_BIN(?)", + voucherId.toString().getBytes()); + + if (deletedRows == 0) { + logger.warn("No voucher with ID: {} was found to delete.", voucherId); + } else { + logger.info("Voucher with ID: {} deleted successfully.", voucherId); + } + } + + static UUID toUUID(byte[] bytes) { + var byteBuffer = ByteBuffer.wrap(bytes); + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/repository/VoucherMemoryRepository.java b/src/main/java/org/prgrms/kdt/voucher/repository/VoucherMemoryRepository.java new file mode 100644 index 0000000000..a62edcaf0d --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/repository/VoucherMemoryRepository.java @@ -0,0 +1,54 @@ +package org.prgrms.kdt.voucher.repository; + +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.domain.PercentDiscountVoucher; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.domain.VoucherDto; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +@Repository +@Profile("local") +public class VoucherMemoryRepository implements VoucherRepository { + + private final Map storage = new ConcurrentHashMap<>(); + private static final Logger logger = LoggerFactory.getLogger(VoucherMemoryRepository.class); + + @Override + public Optional findById(UUID voucherId) { + return Optional.ofNullable(storage.get(voucherId)); + } + + @Override + public Voucher save(VoucherDto voucherDto) { + Voucher voucher = null; + if(voucherDto.getType().equals(FIXED.getType())){ + voucher = FixedAmountVoucher.fromDto(voucherDto); + } + if(voucherDto.getType().equals(PERCENT.getType())){ + voucher = PercentDiscountVoucher.fromDto(voucherDto); + } + storage.put(voucher.getVoucherId(), voucher); + logger.debug("바우처 생성 -> " + voucher.toString()); + return voucher; + } + + @Override + public List findAll() { + logger.debug("모든 바우처의 갯수 : " + storage.size()); + return new ArrayList<>(storage.values()); + } + + @Override + public void deleteById(UUID voucherId) { + + } +} diff --git a/src/main/java/org/prgrms/kdt/voucher/repository/VoucherRepository.java b/src/main/java/org/prgrms/kdt/voucher/repository/VoucherRepository.java new file mode 100644 index 0000000000..c92d5377c9 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/repository/VoucherRepository.java @@ -0,0 +1,19 @@ +package org.prgrms.kdt.voucher.repository; + +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.domain.VoucherDto; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface VoucherRepository { + + Optional findById(UUID voucherId); + + Voucher save(VoucherDto voucherDto); + + List findAll(); + + void deleteById(UUID voucherId); +} diff --git a/src/main/java/org/prgrms/kdt/voucher/service/VoucherService.java b/src/main/java/org/prgrms/kdt/voucher/service/VoucherService.java new file mode 100644 index 0000000000..734c602a8c --- /dev/null +++ b/src/main/java/org/prgrms/kdt/voucher/service/VoucherService.java @@ -0,0 +1,86 @@ +package org.prgrms.kdt.voucher.service; + +import org.prgrms.kdt.voucher.domain.VoucherDto; +import org.prgrms.kdt.voucher.domain.PercentDiscountVoucher; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.repository.VoucherRepository; +import org.prgrms.kdt.wallet.Wallet; +import org.prgrms.kdt.wallet.WalletRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.text.MessageFormat; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.prgrms.kdt.voucher.VoucherMessage.*; +import static org.prgrms.kdt.voucher.VoucherMessage.EXCEPTION_FIXED_AMOUNT_OVER; +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +@Service +public class VoucherService { + + private final VoucherRepository voucherRepository; + private final WalletRepository walletRepository; + private static final Logger logger = LoggerFactory.getLogger(VoucherService.class); + + public VoucherService(VoucherRepository voucherRepository, WalletRepository walletRepository) { + this.voucherRepository = voucherRepository; + this.walletRepository = walletRepository; + } + + public void createFixedAmountVoucher(int amount) { + VoucherDto voucherDto = new VoucherDto(UUID.randomUUID(), amount, FIXED.getType(), LocalDateTime.now()); + voucherRepository.save(voucherDto); + } + + public void createPercentDiscountVoucher(int amount) { + VoucherDto voucherDto = new VoucherDto(UUID.randomUUID(), amount, PERCENT.getType(), LocalDateTime.now()); + voucherRepository.save(voucherDto); + } + + public Voucher getVoucher(UUID voucherId) { + return voucherRepository + .findById(voucherId) + .orElseThrow(() -> { + String errorMessage = MessageFormat.format( "{0}" + EXCEPTION_FIND_VOUCHER.getMessage(), voucherId); + logger.error(errorMessage); + return new RuntimeException(errorMessage); + }); + } + + public List getAllVouchers() { + var voucherList = voucherRepository.findAll(); + if (voucherList.isEmpty()) { + System.out.println(VOUCHER_IS_EMPTY.getMessage()); + } + return voucherList; + } + + public Optional getOwner(UUID voucherId) { + return walletRepository.findByVoucherId(voucherId.toString()); + } + + public void removeVoucherById(UUID voucherId) { + voucherRepository.deleteById(voucherId); + } + + public List getVouchersByCriteria(LocalDateTime startDate, LocalDateTime endDate, String voucherType) { + List vouchersByCriteria = new ArrayList<>(); + List allVouchers = voucherRepository.findAll(); + + for (Voucher voucher : allVouchers) { + if ((voucher.getCreatedAt().isAfter(startDate) || voucher.getCreatedAt().isEqual(startDate)) + && (voucher.getCreatedAt().isBefore(endDate) || voucher.getCreatedAt().isEqual(endDate)) + && voucher.getType().equals(voucherType)) { + vouchersByCriteria.add(voucher); + } + } + return vouchersByCriteria; + } +} diff --git a/src/main/java/org/prgrms/kdt/wallet/Wallet.java b/src/main/java/org/prgrms/kdt/wallet/Wallet.java new file mode 100644 index 0000000000..521eccdbe4 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/wallet/Wallet.java @@ -0,0 +1,42 @@ +package org.prgrms.kdt.wallet; + +import java.util.UUID; + +public class Wallet { + private final String walletId; + private final String customerId; + private final String voucherId; + + Wallet(String customerId, String voucherId) { + this.walletId = UUID.randomUUID().toString(); + this.customerId = customerId; + this.voucherId = voucherId; + } + + public Wallet(String walletId, String customerId, String voucherId) { + this.walletId = walletId; + this.customerId = customerId; + this.voucherId = voucherId; + } + + public String getWalletId() { + return walletId; + } + + public String getCustomerId() { + return customerId; + } + + public String getVoucherId() { + return voucherId; + } + + @Override + public String toString() { + return "Wallet{" + + "walletId='" + walletId + '\'' + + ", customerId='" + customerId + '\'' + + ", voucherId='" + voucherId + '\'' + + '}'; + } +} diff --git a/src/main/java/org/prgrms/kdt/wallet/WalletController.java b/src/main/java/org/prgrms/kdt/wallet/WalletController.java new file mode 100644 index 0000000000..687437f677 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/wallet/WalletController.java @@ -0,0 +1,84 @@ +package org.prgrms.kdt.wallet; + +import org.prgrms.kdt.io.InputHandler; +import org.prgrms.kdt.io.OutputHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +import java.io.IOException; + +import static org.prgrms.kdt.io.SystemMessage.EXCEPTION_NOT_EXIST_MENU; +import static org.prgrms.kdt.wallet.WalletMessage.*; + +@Controller +public class WalletController { + + private final WalletService walletService; + private final InputHandler inputHandler; + private final OutputHandler outputHandler; + + private static final Logger logger = LoggerFactory.getLogger(WalletController.class); + private static final String lineSeparator = System.lineSeparator(); + + private final String CREATE = "create"; + private final String REMOVE = "remove"; + + public WalletController(WalletService walletService, InputHandler inputHandler, OutputHandler outputHandler) { + this.walletService = walletService; + this.inputHandler = inputHandler; + this.outputHandler = outputHandler; + } + + public void walletMenu() throws IOException { + String menu = selectWalletMenu(); + + switch (menu) { + case CREATE: + addWallet(); + break; + case REMOVE: + removeWalletByCustomerId(); + break; + default: + String errorMessage = EXCEPTION_NOT_EXIST_MENU.getMessage(); + logger.error(errorMessage); + outputHandler.outputString(errorMessage); + break; + } + } + + private String selectWalletMenu() throws IOException { + StringBuilder sb = new StringBuilder(); + sb.append("=== Wallet Program ==="); + sb.append(lineSeparator); + sb.append("[1] Type 'create' to create a new wallet."); + sb.append(lineSeparator); + sb.append("[2] Type 'remove' to remove a wallet."); + sb.append(lineSeparator); + + outputHandler.outputString(sb.toString()); + + return inputHandler.inputString(); + } + + private void addWallet() throws IOException { + outputHandler.outputString(ADD_CUSTOMER_ID.getMessage()); + String customerId = inputHandler.inputString(); + outputHandler.outputString(ADD_VOUCHER_ID.getMessage()); + String voucherId = inputHandler.inputString(); + + walletService.addWallet(customerId, voucherId); + + outputHandler.outputString(COMPLETE_ADD.getMessage()); + } + + private void removeWalletByCustomerId() throws IOException { + outputHandler.outputString(REMOVE_CUSTOMER_ID.getMessage()); + String customerId = inputHandler.inputString(); + + walletService.deleteByCustomerId(customerId); + + outputHandler.outputString(COMPLETE_REMOVE.getMessage()); + } +} diff --git a/src/main/java/org/prgrms/kdt/wallet/WalletJdbcRepository.java b/src/main/java/org/prgrms/kdt/wallet/WalletJdbcRepository.java new file mode 100644 index 0000000000..dbd82cd346 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/wallet/WalletJdbcRepository.java @@ -0,0 +1,91 @@ +package org.prgrms.kdt.wallet; + +import org.prgrms.kdt.customer.Customer; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.domain.PercentDiscountVoucher; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.nio.ByteBuffer; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.prgrms.kdt.voucher.VoucherMessage.EXCEPTION_VOUCHER_ROW_MAPPER; +import static org.prgrms.kdt.voucher.domain.VoucherType.FIXED; +import static org.prgrms.kdt.voucher.domain.VoucherType.PERCENT; + +@Repository +public class WalletJdbcRepository implements WalletRepository{ + private static final Logger logger = LoggerFactory.getLogger(WalletJdbcRepository.class); + private final JdbcTemplate jdbcTemplate; + private static final RowMapper walletRowMapper = (resultSet, i) -> { + UUID customerId = toUUID(resultSet.getBytes("customer_id")); + UUID voucherId = toUUID(resultSet.getBytes("voucher_id")); + return new Wallet(customerId.toString(), voucherId.toString()); + }; + + public WalletJdbcRepository(DataSource dataSource, JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Wallet save(Wallet wallet) { + int update = jdbcTemplate.update("INSERT INTO wallets(wallet_id, customer_id, voucher_id) VALUES (UUID_TO_BIN(?), UUID_TO_BIN(?), UUID_TO_BIN(?))", + wallet.getWalletId().toString().getBytes(), + wallet.getCustomerId().toString().getBytes(), + wallet.getVoucherId().toString().getBytes()); + if (update != 1) { + throw new RuntimeException("Noting was inserted"); + } + return wallet; + } + + @Override + public Optional findById(String walletId) { + try { + return Optional.ofNullable(jdbcTemplate.queryForObject("select * from wallets WHERE wallet_id = UUID_TO_BIN(?)", + walletRowMapper, + walletId.getBytes())); + } catch (EmptyResultDataAccessException e) { + logger.error("Got empty result", e); + return Optional.empty(); + } + } + + @Override + public List findByCustomerId(String customerId) { + return jdbcTemplate.query("select * from wallets WHERE customer_id = UUID_TO_BIN(?)", + walletRowMapper, + customerId.getBytes()); + } + + @Override + public Optional findByVoucherId(String voucherId) { + try { + return Optional.ofNullable(jdbcTemplate.queryForObject("select * from wallets WHERE voucher_id = UUID_TO_BIN(?)", + walletRowMapper, + voucherId.getBytes())); + } catch (EmptyResultDataAccessException e) { + logger.error("Got empty result", e); + return Optional.empty(); + } + } + + @Override + public void deleteByCustomerId(String customerId) { + jdbcTemplate.update("DELETE FROM wallets WHERE customer_id = UUID_TO_BIN(?)", customerId.getBytes()); + } + + static UUID toUUID(byte[] bytes) { + var byteBuffer = ByteBuffer.wrap(bytes); + return new UUID(byteBuffer.getLong(), byteBuffer.getLong()); + } +} diff --git a/src/main/java/org/prgrms/kdt/wallet/WalletMessage.java b/src/main/java/org/prgrms/kdt/wallet/WalletMessage.java new file mode 100644 index 0000000000..ae1b412742 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/wallet/WalletMessage.java @@ -0,0 +1,20 @@ +package org.prgrms.kdt.wallet; + +public enum WalletMessage { + + COMPLETE_ADD("지갑 추가가 완료되었습니다."), + COMPLETE_REMOVE("지갑 제거가 완료되었습니다."), + REMOVE_CUSTOMER_ID("바우처를 모두 제거할 고객의 아이디를 입력해주세요 : "), + ADD_CUSTOMER_ID("바우처가 등록될 고객 아이디를 입력해주세요 : "), + ADD_VOUCHER_ID("고객에게 등록할 바우처 아이디를 입력해주세요 : "); + + private final String message; + + public String getMessage() { + return message; + } + + WalletMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/org/prgrms/kdt/wallet/WalletRepository.java b/src/main/java/org/prgrms/kdt/wallet/WalletRepository.java new file mode 100644 index 0000000000..81d701d3fc --- /dev/null +++ b/src/main/java/org/prgrms/kdt/wallet/WalletRepository.java @@ -0,0 +1,16 @@ +package org.prgrms.kdt.wallet; + +import java.util.List; +import java.util.Optional; + +public interface WalletRepository { + Wallet save(Wallet wallet); + + Optional findById(String walletId); + + List findByCustomerId(String customerId); + + Optional findByVoucherId(String voucherId); + + void deleteByCustomerId(String customerId); +} diff --git a/src/main/java/org/prgrms/kdt/wallet/WalletService.java b/src/main/java/org/prgrms/kdt/wallet/WalletService.java new file mode 100644 index 0000000000..a6d8fd3ad3 --- /dev/null +++ b/src/main/java/org/prgrms/kdt/wallet/WalletService.java @@ -0,0 +1,47 @@ +package org.prgrms.kdt.wallet; + +import org.prgrms.kdt.customer.repository.CustomerRepository; +import org.prgrms.kdt.voucher.repository.VoucherRepository; +import org.springframework.stereotype.Service; + +import java.util.UUID; + +import static org.prgrms.kdt.customer.CustomerMessage.EXCEPTION_NOT_EXIST_CUSTOMER; +import static org.prgrms.kdt.voucher.VoucherMessage.EXCEPTION_NOT_EXIST_VOUCHER; + +@Service +public class WalletService { + private final WalletRepository walletRepository; + private final CustomerRepository customerRepository; + private final VoucherRepository voucherRepository; + + public WalletService(WalletRepository walletRepository, CustomerRepository customerRepository, VoucherRepository voucherRepository) { + this.walletRepository = walletRepository; + this.customerRepository = customerRepository; + this.voucherRepository = voucherRepository; + } + + public String addWallet(String customerId, String voucherId) { + validateCustomerExist(customerId); + validateVoucherExist(voucherId); + + Wallet wallet = new Wallet(UUID.randomUUID().toString(), customerId, voucherId); + return walletRepository.save(wallet).getWalletId(); + } + + + public boolean deleteByCustomerId(String customerId) { + walletRepository.deleteByCustomerId(customerId); + return true; + } + + private void validateCustomerExist(String customerId) { + customerRepository.findById(UUID.fromString(customerId)) + .orElseThrow(() -> new RuntimeException(EXCEPTION_NOT_EXIST_CUSTOMER.getMessage())); + } + + private void validateVoucherExist(String voucherId) { + voucherRepository.findById(UUID.fromString(voucherId)) + .orElseThrow(() -> new RuntimeException(EXCEPTION_NOT_EXIST_VOUCHER.getMessage())); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000000..f670bf8610 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,18 @@ +kdt: + version: "v1.0" + minimum-order-amount: 1 + support-vendors: + - a + - b + - c + - d + description: |- + line 1 hello world + line 2 xxxx + line 3 +spring: + profiles: + active: dev + thymeleaf: + prefix: classpath:/templates/ + suffix: .html diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000000..8dd791ec47 --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + +db dD d8888b. d888888b .d8b. d8888b. d8888b. .o88b. db d888888b .d8b. d888888b d888888b .d88b. d8b db +88 ,8P' 88 `8D `~~88~~' d8' `8b 88 `8D 88 `8D d8P Y8 88 `88' d8' `8b `~~88~~' `88' .8P Y8. 888o 88 +88,8P 88 88 88 88ooo88 88oodD' 88oodD' 8P 88 88 88ooo88 88 88 88 88 88V8o 88 +88`8b 88 88 88 88~~~88 88~~~ 88~~~ 8b 88 88 88~~~88 88 88 88 88 88 V8o88 +88 `88. 88 .8D 88 88 88 88 88 Y8b d8 88booo. .88. 88 88 88 .88. `8b d8' 88 V888 +YP YD Y8888D' YP YP YP 88 88 `Y88P' Y88888P Y888888P YP YP YP Y888888P `Y88P' VP V8P + + diff --git a/src/main/resources/customer_blacklist.csv b/src/main/resources/customer_blacklist.csv new file mode 100644 index 0000000000..91ba655393 --- /dev/null +++ b/src/main/resources/customer_blacklist.csv @@ -0,0 +1,4 @@ +f6a39a06-ec14-4d4b-8a71-6a0c97b83be6,John Doe,johndoe@example.com,2023-10-16T10:15:30,true +ac2e569d-b44f-4b0e-83b6-cbbd5d5c4d19,Jane Smith,janesmith@example.com,2023-10-16T14:45:00,false +97c312f8-620a-4e25-8d90-2f75336c05ec,Michael Johnson,michaeljohnson@example.com,2023-10-17T09:30:15,false +e816ff1c-d3eb-4f68-aa4b-0d27a2b14459,Emily Davis,emilydavis@example.com,2023-10-18T15:20:45,true diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000000..6372d7d9ba --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,27 @@ + + + + + + + logs/access.log + + logs/access-%d{yyyy-MM-dd}.log + + + + UTF-8 + ${FILE_LOG_PATTERN} + + + + + + + + + + + + + diff --git a/src/main/resources/templates/customer/customer-new.html b/src/main/resources/templates/customer/customer-new.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/resources/templates/voucher/voucher-create.html b/src/main/resources/templates/voucher/voucher-create.html new file mode 100644 index 0000000000..514a646da3 --- /dev/null +++ b/src/main/resources/templates/voucher/voucher-create.html @@ -0,0 +1,50 @@ + + + + + + Create Voucher + + + + + +
+

Create Voucher

+
+
+ + +
+
+ + +
+ + +
+
+ + + + + diff --git a/src/main/resources/templates/voucher/voucher-list.html b/src/main/resources/templates/voucher/voucher-list.html new file mode 100644 index 0000000000..2df73cce35 --- /dev/null +++ b/src/main/resources/templates/voucher/voucher-list.html @@ -0,0 +1,59 @@ + + + + + Voucher List + + + + + + +
+

Voucher List

+ +
+ + + + + + + + + + + + + + + +
Voucher IDDetailsActions
+
+ +
+
+
+
+ + + diff --git a/src/test/java/org/prgrms/kdt/KdtSpringContextTests.java b/src/test/java/org/prgrms/kdt/KdtSpringContextTests.java new file mode 100644 index 0000000000..dfc9c6737f --- /dev/null +++ b/src/test/java/org/prgrms/kdt/KdtSpringContextTests.java @@ -0,0 +1,79 @@ +package org.prgrms.kdt; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgrms.kdt.order.OrderItem; +import org.prgrms.kdt.order.OrderService; +import org.prgrms.kdt.order.OrderStatus; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.repository.VoucherRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +@SpringJUnitConfig +@ActiveProfiles("local") +public class KdtSpringContextTests { + + @Configuration + @ComponentScan( + basePackages = {"org.prgrms.kdt"} + ) + static class Config { + } + + @Autowired + ApplicationContext context; + + @Autowired + OrderService orderService; + + @Autowired + VoucherRepository voucherRepository; + + @Test + @DisplayName("applicationContext가 생성 되야한다.") + public void testApplicationContext() { + assertThat(context, notNullValue()); + } + + @Test + @DisplayName("VoucherRepository가 빈으로 등록되어 있어야 한다.") + public void testVoucherRepositoryCreation() { + var bean = context.getBean(VoucherRepository.class); + assertThat(bean, notNullValue()); + } + + @Test + @DisplayName("orderService를 사용해서 주문을 생성할 수 있다.") + public void testOrderService() { + // Given + var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100, LocalDateTime.now()); + voucherRepository.save(fixedAmountVoucher); + + // When + var order = orderService.createOrder( + UUID.randomUUID(), + List.of(new OrderItem(UUID.randomUUID(), 200, 1)), + fixedAmountVoucher.getVoucherId()); + + // Then + assertThat(order.totalAmount(), is(100L)); + assertThat(order.getVoucher().isEmpty(), is(false)); + assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId())); + assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED)); + } + + +} diff --git a/src/test/java/org/prgrms/kdt/customer/CustomerJdbcRepositoryTest.java b/src/test/java/org/prgrms/kdt/customer/CustomerJdbcRepositoryTest.java new file mode 100644 index 0000000000..f4ef316ec4 --- /dev/null +++ b/src/test/java/org/prgrms/kdt/customer/CustomerJdbcRepositoryTest.java @@ -0,0 +1,159 @@ +package org.prgrms.kdt.customer; + +import com.wix.mysql.EmbeddedMysql; +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.*; +import org.prgrms.kdt.customer.repository.CustomerJdbcRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import javax.sql.DataSource; +import java.time.LocalDateTime; +import java.util.UUID; + +import static com.wix.mysql.EmbeddedMysql.anEmbeddedMysql; +import static com.wix.mysql.ScriptResolver.classPathScript; +import static com.wix.mysql.config.Charset.UTF8; +import static com.wix.mysql.config.MysqldConfig.aMysqldConfig; +import static com.wix.mysql.distribution.Version.v8_0_11; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringJUnitConfig +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CustomerJdbcRepositoryTest { + + @Configuration + @ComponentScan( + basePackages = {"org.prgrms.kdt.customer"} + ) + static class Config { + + @Bean + public DataSource dataSource() { + var dataSource = DataSourceBuilder.create() + .url("jdbc:mysql://localhost:2215/test-order_mgmt") + .username("test") + .password("test1234!") + .type(HikariDataSource.class) + .build(); + dataSource.setMaximumPoolSize(1000); + dataSource.setMinimumIdle(100); + return dataSource; + } + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + return new JdbcTemplate(dataSource); + } + + } + + @Autowired + CustomerJdbcRepository customerJdbcRepository; + + @Autowired + DataSource dataSource; + + Customer newCustomer; + + EmbeddedMysql embeddedMysql; + + @BeforeAll + void setup() { + newCustomer = new Customer(UUID.randomUUID(), "test-user", "test-user@gmail.com", LocalDateTime.now()); + var mysqlConfig = aMysqldConfig(v8_0_11) + .withCharset(UTF8) + .withPort(2215) + .withUser("test", "test1234!") + .withTimeZone("Asia/Seoul") + .build(); + embeddedMysql = anEmbeddedMysql(mysqlConfig) + .addSchema("test-order_mgmt", classPathScript("schema.sql")) + .start(); +// customerJdbcRepository.deleteAll(); + } + + @AfterAll + void cleanup() { + embeddedMysql.stop(); + } + + @Test + @Order(1) + public void testHikariConnectionPool() { + assertThat(dataSource.getClass().getName(), is("com.zaxxer.hikari.HikariDataSource")); + } + + @Test + @Order(2) + @DisplayName("고객을 추가할 수 있다.") + public void testInsert() { + + try { + customerJdbcRepository.save(newCustomer); + } catch (BadSqlGrammarException e) { + e.getSQLException().getErrorCode(); + } + + + var retrievedCustomer = customerJdbcRepository.findById(newCustomer.getCustomerId()); + assertThat(retrievedCustomer.isEmpty(), is(false)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(newCustomer)); + } + + @Test + @Order(3) + @DisplayName("전체 고객을 조회할 수 있다.") + public void testFindAll() { + var customers = customerJdbcRepository.findAll(); + assertThat(customers.isEmpty(), is(false)); + } + + @Test + @Order(4) + @DisplayName("이름으로 고객을 조회할 수 있다.") + public void testFindByName() { + var customer = customerJdbcRepository.findByName(newCustomer.getName()); + assertThat(customer.isEmpty(), is(false)); + + var unknown = customerJdbcRepository.findByName("unknown-user"); + assertThat(unknown.isEmpty(), is(true)); + } + + @Test + @Order(5) + @DisplayName("이메일로 고객을 조회할 수 있다.") + public void testFindByEmail() { + var customer = customerJdbcRepository.findByEmail(newCustomer.getEmail()); + assertThat(customer.isEmpty(), is(false)); + + var unknown = customerJdbcRepository.findByEmail("unknown-user@gmail.com"); + assertThat(unknown.isEmpty(), is(true)); + } + + @Test + @Order(6) + @DisplayName("고객을 수정할 수 있다.") + public void testUpdate() { + newCustomer.changeName("updated-user"); + customerJdbcRepository.update(newCustomer); + + var all = customerJdbcRepository.findAll(); + assertThat(all, hasSize(1)); + assertThat(all, everyItem(samePropertyValuesAs(newCustomer))); + + var retrievedCustomer = customerJdbcRepository.findById(newCustomer.getCustomerId()); + assertThat(retrievedCustomer.isEmpty(), is(false)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(newCustomer)); + } + + +} diff --git a/src/test/java/org/prgrms/kdt/order/OrderServiceTest.java b/src/test/java/org/prgrms/kdt/order/OrderServiceTest.java new file mode 100644 index 0000000000..60e3265eda --- /dev/null +++ b/src/test/java/org/prgrms/kdt/order/OrderServiceTest.java @@ -0,0 +1,81 @@ +package org.prgrms.kdt.order; + +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.repository.VoucherMemoryRepository; +import org.prgrms.kdt.voucher.service.VoucherService; +import org.prgrms.kdt.wallet.WalletJdbcRepository; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.*; + +class OrderServiceTest { + + class OrderRepositoryStub implements OrderRepository { + @Override + public Order insert(Order order) { + return null; + } + } + + @Test + @DisplayName("오더가 생성되야한다. (stub)") + void createOrder() { + // Given + var voucherRepository = new VoucherMemoryRepository(); + var dataSource = DataSourceBuilder.create().type(HikariDataSource.class).build(); + var jdbcTemplate = new JdbcTemplate(dataSource); + var walletRepository = new WalletJdbcRepository(dataSource, jdbcTemplate); + var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100, LocalDateTime.now()); + VoucherService voucherService = new VoucherService(voucherRepository, walletRepository); + + voucherRepository.save(fixedAmountVoucher); + var sut = new OrderService(voucherService, new OrderRepositoryStub()); + + // When + var order = sut.createOrder(UUID.randomUUID(), List.of(new OrderItem(UUID.randomUUID(), 200, 1)), fixedAmountVoucher.getVoucherId()); + + // Then + assertThat(order.totalAmount(), is(100L)); + assertThat(order.getVoucher().isEmpty(), is(false)); + assertThat(order.getVoucher().get().getVoucherId(), is(fixedAmountVoucher.getVoucherId())); + assertThat(order.getOrderStatus(), is(OrderStatus.ACCEPTED)); + } + + @Test + @DisplayName("오더가 생성되야한다. (mock)") + void createOrderByMock() { + // Given + var voucherServiceMock = mock(VoucherService.class); + var orderRepositoryMock = mock(OrderRepository.class); + var fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 100, LocalDateTime.now()); + when(voucherServiceMock.getVoucher(fixedAmountVoucher.getVoucherId())).thenReturn(fixedAmountVoucher); + var sut = new OrderService(voucherServiceMock, orderRepositoryMock); + + // When + var order = sut.createOrder( + UUID.randomUUID(), + List.of(new OrderItem(UUID.randomUUID(), 200, 1)), + fixedAmountVoucher.getVoucherId()); + + // Then + assertThat(order.totalAmount(), is(100L)); + assertThat(order.getVoucher().isEmpty(), is(false)); + var inOrder = inOrder(voucherServiceMock, orderRepositoryMock); + inOrder.verify(voucherServiceMock).getVoucher(fixedAmountVoucher.getVoucherId()); + inOrder.verify(orderRepositoryMock).insert(order); + inOrder.verify(voucherServiceMock).removeVoucherById(fixedAmountVoucher.getVoucherId()); + + } + + +} diff --git a/src/test/java/org/prgrms/kdt/voucher/FixedAmountVoucherTest.java b/src/test/java/org/prgrms/kdt/voucher/FixedAmountVoucherTest.java new file mode 100644 index 0000000000..c2c08945d1 --- /dev/null +++ b/src/test/java/org/prgrms/kdt/voucher/FixedAmountVoucherTest.java @@ -0,0 +1,66 @@ +package org.prgrms.kdt.voucher; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDateTime; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +class FixedAmountVoucherTest { + + private static final Logger logger = LoggerFactory.getLogger(FixedAmountVoucherTest.class); + + @BeforeAll + static void setup() { + logger.info("@BeforeAll - run once"); + } + + @BeforeEach + void init() { + logger.info("@BeforeEach - run before each test method"); + } + + @Test + @DisplayName("기본적인 assertEqual 테스트 🙏") + void testAssertEqual() { + assertEquals(2, 1 + 1); + } + + @Test + @DisplayName("주어진 금액만큼 할인을 해야한다.") + void testDiscount() { + var sut = new FixedAmountVoucher(UUID.randomUUID(), 100, LocalDateTime.now()); + assertEquals(900, sut.discount(1000)); + } + + @Test + @DisplayName("디스카운트된 금액은 마이너스가 될 수 없다.") + void testMinusDiscountedAmount() { + var sut = new FixedAmountVoucher(UUID.randomUUID(), 1000, LocalDateTime.now()); + assertEquals(0, sut.discount(900)); + } + + @Test + @DisplayName("할인 금액은 마이너스가 될 수 없다.") + void testWithMinus() { + assertThrows(IllegalArgumentException.class, () -> new FixedAmountVoucher(UUID.randomUUID(), -100, LocalDateTime.now())); + } + + @Test + @DisplayName("유효한 할인 금액으로만 생성할 수 있다.") + void testVoucherCreation() { + assertAll("FixedAmountVoucher creation", + () -> assertThrows(IllegalArgumentException.class, () -> new FixedAmountVoucher(UUID.randomUUID(), 0, LocalDateTime.now())), + () -> assertThrows(IllegalArgumentException.class, () -> new FixedAmountVoucher(UUID.randomUUID(), -100, LocalDateTime.now())), + () -> assertThrows(IllegalArgumentException.class, () -> new FixedAmountVoucher(UUID.randomUUID(), 1000000, LocalDateTime.now())) + ); + } + +} diff --git a/src/test/java/org/prgrms/kdt/voucher/HamcrestAssertionTests.java b/src/test/java/org/prgrms/kdt/voucher/HamcrestAssertionTests.java new file mode 100644 index 0000000000..35db86a61a --- /dev/null +++ b/src/test/java/org/prgrms/kdt/voucher/HamcrestAssertionTests.java @@ -0,0 +1,37 @@ +package org.prgrms.kdt.voucher; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +public class HamcrestAssertionTests { + + @Test + @DisplayName("여러 hamcrest matcher 테스트") + void hamcrestTest() { + assertEquals(2, 1 + 1); + assertThat(1 + 1, equalTo(2)); + assertThat(1 + 1, is(2)); + assertThat(1 + 1, anyOf(is(1), is(2))); + + assertNotEquals(1, 1 + 1); + assertThat(1 + 1, not(equalTo(1))); + } + + @Test + @DisplayName("컬렉션에 대한 matcher 테스트") + void hamcrestListMatcherTest() { + var prices = List.of(2, 3, 4); + assertThat(prices, hasSize(3)); + assertThat(prices, everyItem(greaterThan(1))); + assertThat(prices, containsInAnyOrder(3, 4, 2)); + assertThat(prices, hasItem(greaterThanOrEqualTo(2))); + } + +} diff --git a/src/test/java/org/prgrms/kdt/voucher/VoucherJdbcRepositoryTest.java b/src/test/java/org/prgrms/kdt/voucher/VoucherJdbcRepositoryTest.java new file mode 100644 index 0000000000..0de0776ac1 --- /dev/null +++ b/src/test/java/org/prgrms/kdt/voucher/VoucherJdbcRepositoryTest.java @@ -0,0 +1,88 @@ +package org.prgrms.kdt.voucher; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.prgrms.kdt.voucher.domain.FixedAmountVoucher; +import org.prgrms.kdt.voucher.domain.Voucher; +import org.prgrms.kdt.voucher.repository.VoucherJdbcRepository; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class VoucherJdbcRepositoryTest { + + @Mock + private JdbcTemplate jdbcTemplate; + + @InjectMocks + private VoucherJdbcRepository voucherJdbcRepository; + + @Test + @DisplayName("id로 바우처를 찾을 수 있어야한다.") + public void givenVoucherId_whenFindById_thenReturnVoucher() { + // Mock the behavior of JdbcTemplate.queryForObject to return a Voucher + when(jdbcTemplate.queryForObject(any(String.class), any(RowMapper.class), any(Object.class))).thenReturn( + new FixedAmountVoucher(UUID.randomUUID(), 50, LocalDateTime.now()) + ); + + UUID voucherId = UUID.randomUUID(); + Optional voucher = voucherJdbcRepository.findById(voucherId); + assertThat(voucher).isPresent(); + } + + @Test + public void givenEmptyVoucherId_whenFindById_thenReturnEmptyOptional() { + // Mock the behavior of JdbcTemplate.queryForObject to return EmptyResultDataAccessException + when(jdbcTemplate.queryForObject(any(String.class), any(RowMapper.class), any(Object.class))) + .thenThrow(new EmptyResultDataAccessException(1)); + + UUID voucherId = UUID.randomUUID(); + Optional voucher = voucherJdbcRepository.findById(voucherId); + assertThat(voucher).isEmpty(); + } + + @Test + public void givenVoucher_whenSave_thenReturnSavedVoucher() { + Voucher fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 50, LocalDateTime.now()); + when(jdbcTemplate.update(any(String.class), any(Object.class))).thenReturn(1); + + Voucher savedVoucher = voucherJdbcRepository.save(fixedAmountVoucher); + assertThat(savedVoucher).isEqualTo(fixedAmountVoucher); + } + + @Test + public void givenVoucher_whenSave_thenThrowRuntimeException() { + FixedAmountVoucher fixedAmountVoucher = new FixedAmountVoucher(UUID.randomUUID(), 50, LocalDateTime.now()); + when(jdbcTemplate.update(any(String.class), any(Object.class))).thenReturn(0); + + assertThatThrownBy(() -> voucherJdbcRepository.save(fixedAmountVoucher)) + .isInstanceOf(RuntimeException.class) + .hasMessage("Nothing was inserted"); + } + + @Test + public void whenFindAll_thenReturnListOfVouchers() { + // Mock the behavior of JdbcTemplate.query to return a list of Vouchers + when(jdbcTemplate.query(any(String.class), any(RowMapper.class))).thenReturn( + List.of(new FixedAmountVoucher(UUID.randomUUID(), 50, LocalDateTime.now())) + ); + + List vouchers = voucherJdbcRepository.findAll(); + assertThat(vouchers).hasSize(1); + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..b0f8b7c2c7 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/sample.txt b/test/sample.txt new file mode 100644 index 0000000000..126129b2ee --- /dev/null +++ b/test/sample.txt @@ -0,0 +1,2 @@ +hello I'm a sample file! +Hi hi \ No newline at end of file