diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..caf5bfb169 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +logs \ No newline at end of file diff --git a/blacklist.csv b/blacklist.csv new file mode 100644 index 0000000000..2b39d63eb8 --- /dev/null +++ b/blacklist.csv @@ -0,0 +1,2 @@ +id,name +1,주환 diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..d4332a0bc2 --- /dev/null +++ b/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.4' + id 'io.spring.dependency-management' version '1.1.3' +} + +group = 'com.programmers' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + // Display name 쓰기 위해 + implementation 'org.junit.jupiter:junit-jupiter:5.8.1' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // lombok + implementation 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + // DB + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + + // mysql + implementation 'com.mysql:mysql-connector-j' + + // web + implementation 'org.springframework.boot:spring-boot-starter-web' + + // thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + + // validation + implementation 'org.springframework.boot:spring-boot-starter-validation' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..7f93135c49 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..ac72c34e8a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..0adc8e1a53 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +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 + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..93e3f59f13 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem 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, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..20365b4d8f --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'springboot-basic' diff --git a/src/main/java/com/programmers/springbootbasic/AppConfig.java b/src/main/java/com/programmers/springbootbasic/AppConfig.java new file mode 100644 index 0000000000..31c9dc5a5a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/AppConfig.java @@ -0,0 +1,18 @@ +package com.programmers.springbootbasic; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.web.filter.HiddenHttpMethodFilter; +@Profile(value = "tomcat") +@Configuration +public class AppConfig { + + @Bean + public FilterRegistrationBean hiddenHttpMethodFilter() { + FilterRegistrationBean filterRegBean = new FilterRegistrationBean<>(); + filterRegBean.setFilter(new HiddenHttpMethodFilter()); + return filterRegBean; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/VoucherApplication.java b/src/main/java/com/programmers/springbootbasic/VoucherApplication.java new file mode 100644 index 0000000000..358c4f8299 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/VoucherApplication.java @@ -0,0 +1,47 @@ +package com.programmers.springbootbasic; + +import com.programmers.springbootbasic.exception.AppExceptionHandler; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Profile; + +@SpringBootApplication +public class VoucherApplication { + private final ConfigurableApplicationContext applicationContext; + + public VoucherApplication(ConfigurableApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + public static void main(String[] args) { + SpringApplication.run( + VoucherApplication.class, args + ); + } + + @Profile(value = "command") + @Bean + public void init() { + startAppWithErrorHandler(applicationContext); + } + + @Profile(value = "tomcat") + @Bean + public ServletWebServerFactory servletTomcatServerContainer() { + return new TomcatServletWebServerFactory(); + } + + private static void startAppWithErrorHandler( + ConfigurableApplicationContext applicationContext + ) { + AppExceptionHandler exceptionHandler = applicationContext.getBean( + AppExceptionHandler.class); + do { + exceptionHandler.handle(); + } while (applicationContext.isActive()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/common/BaseEntity.java b/src/main/java/com/programmers/springbootbasic/common/BaseEntity.java new file mode 100644 index 0000000000..fb11fbb7ff --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/common/BaseEntity.java @@ -0,0 +1,33 @@ +package com.programmers.springbootbasic.common; + +import java.time.LocalDateTime; +import java.util.Objects; + +public abstract class BaseEntity { + + private final LocalDateTime createdAt; + + protected BaseEntity(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BaseEntity that)) { + return false; + } + return Objects.equals(getCreatedAt(), that.getCreatedAt()); + } + + @Override + public int hashCode() { + return Objects.hash(getCreatedAt()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/common/IdGenerator.java b/src/main/java/com/programmers/springbootbasic/common/IdGenerator.java new file mode 100644 index 0000000000..3c2ccda736 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/common/IdGenerator.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.common; + +import java.util.UUID; + +public interface IdGenerator { + + UUID generate(); + +} diff --git a/src/main/java/com/programmers/springbootbasic/common/Repository.java b/src/main/java/com/programmers/springbootbasic/common/Repository.java new file mode 100644 index 0000000000..ea6f7b0e9c --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/common/Repository.java @@ -0,0 +1,17 @@ +package com.programmers.springbootbasic.common; + +import java.util.List; +import java.util.Optional; + +public interface Repository { + + T save(T entity); + + Optional findById(ID id); + + List findAll(); + + int deleteById(ID id); + + int update(T entity); +} diff --git a/src/main/java/com/programmers/springbootbasic/common/TimeGenerator.java b/src/main/java/com/programmers/springbootbasic/common/TimeGenerator.java new file mode 100644 index 0000000000..85446b5e27 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/common/TimeGenerator.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.common; + +import java.time.LocalDateTime; + +public interface TimeGenerator { + + LocalDateTime now(); + +} diff --git a/src/main/java/com/programmers/springbootbasic/common/dto/BaseListCountResponse.java b/src/main/java/com/programmers/springbootbasic/common/dto/BaseListCountResponse.java new file mode 100644 index 0000000000..a6198f5657 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/common/dto/BaseListCountResponse.java @@ -0,0 +1,13 @@ +package com.programmers.springbootbasic.common.dto; + +import java.util.List; + +public class BaseListCountResponse extends BaseListResponse { + + private final int count; + + public BaseListCountResponse(List data, int count) { + super(data); + this.count = count; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/common/dto/BaseListResponse.java b/src/main/java/com/programmers/springbootbasic/common/dto/BaseListResponse.java new file mode 100644 index 0000000000..8c6048837a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/common/dto/BaseListResponse.java @@ -0,0 +1,16 @@ +package com.programmers.springbootbasic.common.dto; + +import java.util.List; + +public class BaseListResponse { + + private final List data; + + public BaseListResponse(List data) { + this.data = data; + } + + public static BaseListResponse from(List data) { + return new BaseListResponse<>(data); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/application/UserService.java b/src/main/java/com/programmers/springbootbasic/domain/user/application/UserService.java new file mode 100644 index 0000000000..52929f3391 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/application/UserService.java @@ -0,0 +1,42 @@ +package com.programmers.springbootbasic.domain.user.application; + +import static com.programmers.springbootbasic.exception.ErrorCode.DUPLICATED_USER; + +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.presentation.dto.CreateUserRequest; +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import com.programmers.springbootbasic.exception.exceptionClass.UserException; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class UserService { + + private final UserRepository userRepository; + + public UserService( + UserRepository userRepository + ) { + this.userRepository = userRepository; + } + + @Transactional + public Long create(CreateUserRequest request) { + // 유효성 검사 + if (userRepository.findByNickname(request.getNickname()).isPresent()) { + throw new UserException(DUPLICATED_USER); + } + var result = userRepository.save(request.toEntity()); + return result.getId(); + } + + public List findBlacklistedUsers() { + return userRepository.findBlacklistedUsers() + .stream() + .map(UserResponse::of) + .toList(); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/domain/UserRepository.java b/src/main/java/com/programmers/springbootbasic/domain/user/domain/UserRepository.java new file mode 100644 index 0000000000..64815052d2 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/domain/UserRepository.java @@ -0,0 +1,13 @@ +package com.programmers.springbootbasic.domain.user.domain; + +import com.programmers.springbootbasic.common.Repository; +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import java.util.List; +import java.util.Optional; + +public interface UserRepository extends Repository { + + List findBlacklistedUsers(); + + Optional findByNickname(String nickname); +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/domain/entity/User.java b/src/main/java/com/programmers/springbootbasic/domain/user/domain/entity/User.java new file mode 100644 index 0000000000..2183849446 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/domain/entity/User.java @@ -0,0 +1,46 @@ +package com.programmers.springbootbasic.domain.user.domain.entity; + +import java.util.Objects; + +public class User { + + private final Long id; + private final String nickname; + private final boolean blocked; + + public User(Long id, String nickname, boolean blocked) { + this.id = id; + this.nickname = nickname; + this.blocked = blocked; + } + + public Long getId() { + return id; + } + + public String getNickname() { + return nickname; + } + + public boolean isBlocked() { + return blocked; + } + + // distinct()를 위해 equals와 hashCode를 재정의 해야 한다. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof User user)) { + return false; + } + return isBlocked() == user.isBlocked() && Objects.equals(getId(), user.getId()) + && Objects.equals(getNickname(), user.getNickname()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getNickname(), isBlocked()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/infrastructure/JdbcUserRepository.java b/src/main/java/com/programmers/springbootbasic/domain/user/infrastructure/JdbcUserRepository.java new file mode 100644 index 0000000000..7d21444aeb --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/infrastructure/JdbcUserRepository.java @@ -0,0 +1,89 @@ +package com.programmers.springbootbasic.domain.user.infrastructure; + +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import java.sql.PreparedStatement; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcUserRepository implements UserRepository { + + private static final String TABLE_NAME = "users"; + private static final String ID = "id"; + private static final String NICKNAME = "nickname"; + private static final String BLOCKED = "blocked"; + + private final JdbcTemplate jdbcTemplate; + + public JdbcUserRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + private final RowMapper userRowMapper = + (rs, rowNum) -> new User(rs.getLong(ID), rs.getString(NICKNAME), rs.getBoolean(BLOCKED)); + + @Override + public User save(User entity) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection + .prepareStatement( + String.format("INSERT INTO %s (%s, %s) VALUES (?, ?)", TABLE_NAME, NICKNAME, + BLOCKED), + new String[]{ID}); + ps.setString(1, entity.getNickname()); + ps.setBoolean(2, entity.isBlocked()); + return ps; + }, keyHolder); + + // key 없으면 npe + return new User(Objects.requireNonNull(keyHolder.getKey()).longValue(), + entity.getNickname(), entity.isBlocked()); + } + + @Override + public Optional findById(Long id) { + List users = jdbcTemplate.query( + String.format("SELECT * FROM %s WHERE %s = ?", TABLE_NAME, ID), userRowMapper, id); + return users.stream().findFirst(); + } + + @Override + public List findAll() { + return jdbcTemplate.query(String.format("SELECT * FROM %s", TABLE_NAME), userRowMapper); + } + + @Override + public int deleteById(Long id) { + return jdbcTemplate.update(String.format("DELETE FROM %s WHERE %s = ?", TABLE_NAME, ID), + id); + } + + @Override + public int update(User entity) { + return jdbcTemplate.update( + String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?", TABLE_NAME, NICKNAME, + BLOCKED, ID), + entity.getNickname(), entity.isBlocked(), entity.getId()); + } + + @Override + public List findBlacklistedUsers() { + return jdbcTemplate.query( + String.format("SELECT * FROM %s WHERE %s = true", TABLE_NAME, BLOCKED), userRowMapper); + } + + @Override + public Optional findByNickname(String nickname) { + return jdbcTemplate.query( + String.format("SELECT * FROM %s WHERE %s = ?", TABLE_NAME, NICKNAME), userRowMapper, + nickname).stream().findFirst(); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/infrastructure/dto/CsvUser.java b/src/main/java/com/programmers/springbootbasic/domain/user/infrastructure/dto/CsvUser.java new file mode 100644 index 0000000000..00cb0fd8ce --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/infrastructure/dto/CsvUser.java @@ -0,0 +1,37 @@ +package com.programmers.springbootbasic.domain.user.infrastructure.dto; + +import com.programmers.springbootbasic.domain.user.domain.entity.User; + +public class CsvUser { + + private String id; + private String name; + private String blocked; + + public CsvUser() { + } + + public CsvUser(String id, String name, String blocked) { + this.id = id; + this.name = name; + this.blocked = blocked; + } + + public static CsvUser of(User user) { + return new CsvUser(user.getId().toString(), user.getNickname(), + String.valueOf(user.isBlocked())); + } + + public User toEntity() { + return new User(Long.valueOf(id), name, Boolean.getBoolean(blocked)); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/presentation/UserController.java b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/UserController.java new file mode 100644 index 0000000000..45de6f7c03 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/UserController.java @@ -0,0 +1,29 @@ +package com.programmers.springbootbasic.domain.user.presentation; + +import com.programmers.springbootbasic.domain.user.application.UserService; +import com.programmers.springbootbasic.domain.user.presentation.dto.CreateUserRequest; +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import jakarta.validation.Valid; +import java.util.List; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; + +@Controller +@Validated +public class UserController { + + private final UserService userService; + + public UserController(UserService userService) { + this.userService = userService; + } + + public void createUser(@Valid CreateUserRequest request) { + userService.create(request); + } + + public List getBlackList() { + return userService.findBlacklistedUsers(); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/presentation/UserThymeController.java b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/UserThymeController.java new file mode 100644 index 0000000000..0353ae30ae --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/UserThymeController.java @@ -0,0 +1,44 @@ +package com.programmers.springbootbasic.domain.user.presentation; + +import com.programmers.springbootbasic.domain.user.application.UserService; +import com.programmers.springbootbasic.domain.user.presentation.dto.CreateUserRequest; +import jakarta.validation.Valid; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/user") +public class UserThymeController { + + private final UserService userService; + + public UserThymeController(UserService userService) { + this.userService = userService; + } + + @GetMapping("/register") + public String getRegisterPage() { + return "user/register"; + } + + @PostMapping("/register") + public String createUser(@Valid @ModelAttribute CreateUserRequest request, Model model) { + Long id = userService.create(request); + + model.addAttribute("userNickname", request.getNickname()); + model.addAttribute("userId", id); + + return "user/registrationComplete"; + } + + @GetMapping + public String getBlackList(Model model) { + var blacklistedUsers = userService.findBlacklistedUsers(); + model.addAttribute("blacklistedUsers", blacklistedUsers); + return "user/blacklistedUsers"; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/presentation/dto/CreateUserRequest.java b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/dto/CreateUserRequest.java new file mode 100644 index 0000000000..801db820e1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/dto/CreateUserRequest.java @@ -0,0 +1,29 @@ +package com.programmers.springbootbasic.domain.user.presentation.dto; + +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.Length; + + +public class CreateUserRequest { + @NotNull + @Length(min = 1, max = 10) + private final String nickname; + + private CreateUserRequest(String nickname) { + this.nickname = nickname; + } + + public String getNickname() { + return nickname; + } + + public static CreateUserRequest of(String nickname) { + return new CreateUserRequest(nickname); + } + + public User toEntity() { + return new User(null, nickname, false); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/user/presentation/dto/UserResponse.java b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/dto/UserResponse.java new file mode 100644 index 0000000000..ed47aaffb9 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/user/presentation/dto/UserResponse.java @@ -0,0 +1,45 @@ +package com.programmers.springbootbasic.domain.user.presentation.dto; + +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import java.util.Objects; + +public class UserResponse { + + private final String nickname; + + public UserResponse(String nickname) { + this.nickname = nickname; + } + + public static UserResponse of(User user) { + return new UserResponse(user.getNickname()); + } + + @Override + public String toString() { + return """ + name = %s + =============================== + """.formatted(nickname); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UserResponse that)) { + return false; + } + return Objects.equals(nickname, that.nickname); + } + + @Override + public int hashCode() { + return Objects.hash(nickname); + } + + public String getNickname() { + return nickname; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/application/UserVoucherWalletService.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/application/UserVoucherWalletService.java new file mode 100644 index 0000000000..60e0a60dda --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/application/UserVoucherWalletService.java @@ -0,0 +1,89 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.application; + +import static com.programmers.springbootbasic.exception.ErrorCode.FAIL_TO_DELETE_USER_VOUCHER; +import static com.programmers.springbootbasic.exception.ErrorCode.NOT_FOUND_USER; +import static com.programmers.springbootbasic.exception.ErrorCode.NOT_FOUND_USER_VOUCHER; +import static com.programmers.springbootbasic.exception.ErrorCode.NOT_FOUND_VOUCHER; + +import com.programmers.springbootbasic.common.TimeGenerator; +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.UserVoucherWalletRepository; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.UserOwnedVoucherResponse; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithUser; +import com.programmers.springbootbasic.exception.exceptionClass.UserException; +import com.programmers.springbootbasic.exception.exceptionClass.UserVoucherWalletException; +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class UserVoucherWalletService { + + private static final int AFFECTED_ROW_ONE = 1; + private final UserVoucherWalletRepository userVoucherWalletRepository; + private final UserRepository userRepository; + private final VoucherRepository voucherRepository; + private final TimeGenerator timeGenerator; + + public UserVoucherWalletService( + UserVoucherWalletRepository userVoucherWalletRepository, + UserRepository userRepository, + VoucherRepository voucherRepository, + TimeGenerator timeGenerator + ) { + this.userVoucherWalletRepository = userVoucherWalletRepository; + this.userRepository = userRepository; + this.voucherRepository = voucherRepository; + this.timeGenerator = timeGenerator; + } + + @Transactional + public Long create(CreateUserVoucherWalletRequest request) { + // 유효성 검사 + var user = userRepository.findByNickname(request.getNickname()) + .orElseThrow(() -> new UserException(NOT_FOUND_USER)); + var voucher = voucherRepository.findById(request.getVoucherId()) + .orElseThrow(() -> new VoucherException(NOT_FOUND_VOUCHER)); + + var nowTime = timeGenerator.now(); + var result = userVoucherWalletRepository.save(request.toEntity(user.getId(), nowTime)); + return result.getId(); + } + + public List findUserByVoucherId(UUID voucherId) { + voucherRepository.findById(voucherId) + .orElseThrow(() -> new VoucherException(NOT_FOUND_VOUCHER)); + return userVoucherWalletRepository.findUserByVoucherId(voucherId).stream() + .map(UserVoucherWalletWithUser::getUser) + .distinct() + .map(UserResponse::of) + .toList(); + } + + public List findVoucherByUserNickname(String nickname) { + var user = userRepository.findByNickname(nickname) + .orElseThrow(() -> new UserException(NOT_FOUND_USER)); + + return userVoucherWalletRepository.findVoucherByUserId(user.getId()).stream() + .map(dto -> UserOwnedVoucherResponse.of(dto.getId(), dto.getVoucher())) + .toList(); + } + + @Transactional + public void deleteById(Long id) { + userVoucherWalletRepository.findById(id) + .orElseThrow(() -> new UserVoucherWalletException(NOT_FOUND_USER_VOUCHER)); + + int affectedRow = userVoucherWalletRepository.deleteById(id); + if (affectedRow != AFFECTED_ROW_ONE) { + throw new UserVoucherWalletException(FAIL_TO_DELETE_USER_VOUCHER); + } + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/domain/UserVoucherWalletRepository.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/domain/UserVoucherWalletRepository.java new file mode 100644 index 0000000000..edf4e5edf6 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/domain/UserVoucherWalletRepository.java @@ -0,0 +1,15 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.domain; + +import com.programmers.springbootbasic.common.Repository; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.entity.UserVoucherWallet; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithUser; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithVoucher; +import java.util.List; +import java.util.UUID; + +public interface UserVoucherWalletRepository extends Repository { + + List findVoucherByUserId(Long userId); + + List findUserByVoucherId(UUID voucherId); +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/domain/entity/UserVoucherWallet.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/domain/entity/UserVoucherWallet.java new file mode 100644 index 0000000000..ddaa5bbfc2 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/domain/entity/UserVoucherWallet.java @@ -0,0 +1,54 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.domain.entity; + +import com.programmers.springbootbasic.common.BaseEntity; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.UUID; + +//TODO: 이름 변경 +public class UserVoucherWallet extends BaseEntity { + + private final Long id; + private final Long userId; + private final UUID voucherId; + + public UserVoucherWallet(Long id, Long userId, UUID voucherId, LocalDateTime createdAt) { + super(createdAt); + this.id = id; + this.userId = userId; + this.voucherId = voucherId; + } + + public UserVoucherWallet(Long userId, UUID voucherId, LocalDateTime createdAt) { + this(null, userId, voucherId, createdAt); + } + + public Long getId() { + return id; + } + + public Long getUserId() { + return userId; + } + + public UUID getVoucherId() { + return voucherId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UserVoucherWallet that)) { + return false; + } + return Objects.equals(getId(), that.getId()) && Objects.equals(getUserId(), + that.getUserId()) && Objects.equals(getVoucherId(), that.getVoucherId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getUserId(), getVoucherId()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/infrastructure/JdbcUserVoucherWalletRepository.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/infrastructure/JdbcUserVoucherWalletRepository.java new file mode 100644 index 0000000000..1af8f0215e --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/infrastructure/JdbcUserVoucherWalletRepository.java @@ -0,0 +1,129 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.infrastructure; + +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.UserVoucherWalletRepository; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.entity.UserVoucherWallet; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithUser; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithVoucher; +import com.programmers.springbootbasic.util.SqlConverter; +import java.sql.PreparedStatement; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcUserVoucherWalletRepository implements UserVoucherWalletRepository { + + private final JdbcTemplate jdbcTemplate; + private final RowMapper voucherWalletRowMapper = + (rs, rowNum) -> new UserVoucherWallet( + rs.getLong("id"), + rs.getLong("user_id"), + UUID.fromString(rs.getString("voucher_id")), + SqlConverter.toLocalDateTime(rs.getString("created_at")) + ); + + public JdbcUserVoucherWalletRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public UserVoucherWallet save(UserVoucherWallet entity) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(connection -> { + PreparedStatement ps = connection + .prepareStatement( + "INSERT INTO user_voucher_wallet (user_id, voucher_id, created_at) VALUES (?, ?, ?)", + new String[]{"id"}); + ps.setLong(1, entity.getUserId()); + ps.setString(2, entity.getVoucherId().toString()); + ps.setTimestamp(3, java.sql.Timestamp.valueOf(entity.getCreatedAt())); + return ps; + }, keyHolder); + + // key 없으면 npe + return new UserVoucherWallet( + Objects.requireNonNull(keyHolder.getKey()).longValue(), + entity.getUserId(), + entity.getVoucherId(), + entity.getCreatedAt() + ); + } + + @Override + public Optional findById(Long id) { + return jdbcTemplate.query("SELECT * FROM user_voucher_wallet WHERE id = ?", + voucherWalletRowMapper, id).stream().findFirst(); + } + + @Override + public List findAll() { + return jdbcTemplate.query("SELECT * FROM user_voucher_wallet", voucherWalletRowMapper); + } + + @Override + public int deleteById(Long id) { + return jdbcTemplate.update("DELETE FROM user_voucher_wallet WHERE id = ?", id); + } + + @Override + public int update(UserVoucherWallet entity) { + return jdbcTemplate.update( + "UPDATE user_voucher_wallet SET user_id = ? ,voucher_id = ? WHERE id = ?", + entity.getUserId(), + entity.getVoucherId().toString(), + entity.getId() + ); + } + + @Override + public List findVoucherByUserId(Long userId) { + RowMapper voucherWalletJoinRowMapper = + (rs, rowNum) -> new UserVoucherWalletWithVoucher( + rs.getLong("id"), + new Voucher( + UUID.fromString(rs.getString("voucher_id")), + VoucherTypeEnum.of(rs.getString("voucher_type")) + .getVoucherType(rs.getInt("benefit_value")), + rs.getInt("benefit_value"), + SqlConverter.toLocalDateTime(rs.getString("created_at")) + ) + ); + + return jdbcTemplate.query(""" + SELECT * + FROM user_voucher_wallet uvw + JOIN vouchers v ON uvw.voucher_id = v.id + WHERE uvw.user_id = ? + """, + voucherWalletJoinRowMapper, userId); + } + + @Override + public List findUserByVoucherId(UUID voucherId) { + RowMapper voucherWalletJoinRowMapper = + (rs, rowNum) -> new UserVoucherWalletWithUser( + rs.getLong("id"), + new User( + rs.getLong("user_id"), + rs.getString("nickname"), + rs.getBoolean("blocked") + ) + ); + return jdbcTemplate.query(""" + SELECT * + FROM user_voucher_wallet uvw + JOIN users u ON uvw.user_id = u.id + WHERE uvw.voucher_id = ? + """, + voucherWalletJoinRowMapper, voucherId.toString()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/UserVoucherWalletController.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/UserVoucherWalletController.java new file mode 100644 index 0000000000..01dab2f4f1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/UserVoucherWalletController.java @@ -0,0 +1,46 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.presentation; + +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import com.programmers.springbootbasic.domain.userVoucherWallet.application.UserVoucherWalletService; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.UserOwnedVoucherResponse; +import com.programmers.springbootbasic.util.ConsoleValidator; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; + +@Controller +@Validated +public class UserVoucherWalletController { + + private final UserVoucherWalletService userVoucherWalletService; + private final ConsoleValidator consoleValidator; + + public UserVoucherWalletController(UserVoucherWalletService userVoucherWalletService, + ConsoleValidator consoleValidator + ) { + this.userVoucherWalletService = userVoucherWalletService; + this.consoleValidator = consoleValidator; + } + + public void createUserVoucher(@Valid CreateUserVoucherWalletRequest request) { + consoleValidator.validate(request); + userVoucherWalletService.create(request); + } + + public void deleteUserVoucher(@NotNull Long id) { + userVoucherWalletService.deleteById(id); + } + + public List findUserByVoucherId(@NotNull UUID voucherId) { + return userVoucherWalletService.findUserByVoucherId(voucherId); + } + + public List findVoucherByUserNickname(@NotEmpty String nickname) { + return userVoucherWalletService.findVoucherByUserNickname(nickname); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/UserVoucherWalletThymeController.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/UserVoucherWalletThymeController.java new file mode 100644 index 0000000000..b114fa745f --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/UserVoucherWalletThymeController.java @@ -0,0 +1,86 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.presentation; + +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import com.programmers.springbootbasic.domain.userVoucherWallet.application.UserVoucherWalletService; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.UserOwnedVoucherResponse; +import com.programmers.springbootbasic.domain.voucher.application.VoucherService; +import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +@RequestMapping("/userVoucher") +public class UserVoucherWalletThymeController { + + private final UserVoucherWalletService userVoucherWalletService; + private final VoucherService voucherService; + + public UserVoucherWalletThymeController( + UserVoucherWalletService userVoucherWalletService, + VoucherService voucherService + ) { + this.userVoucherWalletService = userVoucherWalletService; + this.voucherService = voucherService; + } + + @GetMapping("/register") + public String getRegisterPage(Model model) { + var vouchers = voucherService.findAll(); + model.addAttribute("vouchers", vouchers); + return "userVoucher/register"; + } + + @PostMapping("/register") + public String registerUserVoucher( + @Valid @ModelAttribute CreateUserVoucherWalletRequest request, Model model + ) { + Long id = userVoucherWalletService.create(request); + model.addAttribute("id", id); + return "userVoucher/registrationComplete"; + } + + @DeleteMapping ("/delete/{id}") + public String deleteUserVoucher(@PathVariable Long id) { + userVoucherWalletService.deleteById(id); + return "userVoucher/deleteComplete"; + } + + @GetMapping("/userByVoucher") + public String getUserByVoucherIdPage(Model model) { + var vouchers = voucherService.findAll(); + model.addAttribute("vouchers", vouchers); + return "userVoucher/enterVoucherId"; + } + + @GetMapping("/userByVoucher/{voucherId}") + public String getUserByVoucherId(@PathVariable UUID voucherId, Model model) { + List users = userVoucherWalletService.findUserByVoucherId(voucherId); + model.addAttribute("users", users); + return "userVoucher/userByVoucherIdResult"; + } + + @GetMapping("/voucherByUser") + public String getVoucherByUserNicknamePage() { + return "userVoucher/enterUserNickname"; + } + + @GetMapping("/voucherByUser/{userNickname}") + public String getVoucherByUserNickname( + @PathVariable String userNickname, Model model + ) { + List vouchers = userVoucherWalletService.findVoucherByUserNickname( + userNickname); + model.addAttribute("vouchers", vouchers); + return "userVoucher/voucherByUserNicknameResult"; + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/dto/CreateUserVoucherWalletRequest.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/dto/CreateUserVoucherWalletRequest.java new file mode 100644 index 0000000000..4d5053e14b --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/dto/CreateUserVoucherWalletRequest.java @@ -0,0 +1,37 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto; + +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.entity.UserVoucherWallet; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.UUID; +import org.hibernate.validator.constraints.Length; + +public class CreateUserVoucherWalletRequest { + + @NotNull + @Length(min = 1, max = 10) + private final String nickname; + @NotNull + private final UUID voucherId; + + public CreateUserVoucherWalletRequest(String nickname, UUID voucherId) { + this.nickname = nickname; + this.voucherId = voucherId; + } + + public static CreateUserVoucherWalletRequest of(String nickname, UUID voucherId) { + return new CreateUserVoucherWalletRequest(nickname, voucherId); + } + + public UserVoucherWallet toEntity(Long userId, LocalDateTime createdAt) { + return new UserVoucherWallet(userId, voucherId, createdAt); + } + + public String getNickname() { + return nickname; + } + + public UUID getVoucherId() { + return voucherId; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/dto/UserOwnedVoucherResponse.java b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/dto/UserOwnedVoucherResponse.java new file mode 100644 index 0000000000..c46a02d2ee --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/userVoucherWallet/presentation/dto/UserOwnedVoucherResponse.java @@ -0,0 +1,36 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; + +public class UserOwnedVoucherResponse { + + private final Long id; + private final VoucherResponse voucherResponse; + + public UserOwnedVoucherResponse(Long id, VoucherResponse voucherResponse) { + this.id = id; + this.voucherResponse = voucherResponse; + } + + public static UserOwnedVoucherResponse of(Long id, Voucher voucher) { + return new UserOwnedVoucherResponse(id, VoucherResponse.of(voucher)); + } + + @Override + public String toString() { + return """ + UserVoucher id = %s + ---Voucher Info--- + %s + """.formatted(id, voucherResponse); + } + + public Long getId() { + return id; + } + + public VoucherResponse getVoucherResponse() { + return voucherResponse; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/application/VoucherService.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/application/VoucherService.java new file mode 100644 index 0000000000..c2868def45 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/application/VoucherService.java @@ -0,0 +1,88 @@ +package com.programmers.springbootbasic.domain.voucher.application; + +import static com.programmers.springbootbasic.exception.ErrorCode.FAIL_TO_DELETE_VOUCHER; +import static com.programmers.springbootbasic.exception.ErrorCode.FAIL_TO_UPDATE_VOUCHER; +import static com.programmers.springbootbasic.exception.ErrorCode.NOT_FOUND_VOUCHER; + +import com.programmers.springbootbasic.common.TimeGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.UpdateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional(readOnly = true) +public class VoucherService { + + private static final int AFFECTED_ROW_ONE = 1; + private final VoucherRepository voucherRepository; + private final VoucherIdGenerator idGenerator; + private final TimeGenerator timeGenerator; + + public VoucherService( + VoucherRepository voucherRepository, + VoucherIdGenerator idGenerator, + TimeGenerator timeGenerator + ) { + this.voucherRepository = voucherRepository; + this.idGenerator = idGenerator; + this.timeGenerator = timeGenerator; + } + + @Transactional + public UUID create(CreateVoucherRequest request) { + Voucher voucher = request.toEntity(idGenerator.generate(), timeGenerator.now()); + voucherRepository.save(voucher); + return voucher.getId(); + } + + public List findAll() { + return voucherRepository.findAll().stream() + .map(VoucherResponse::of) + .toList(); + } + + public VoucherResponse findById(UUID id) { + return voucherRepository.findById(id) + .map(VoucherResponse::of) + .orElseThrow(() -> new VoucherException(NOT_FOUND_VOUCHER)); + } + + @Transactional + public void deleteById(UUID id) { + voucherRepository.findById(id).orElseThrow(() -> new VoucherException(NOT_FOUND_VOUCHER)); + + if (voucherRepository.deleteById(id) != AFFECTED_ROW_ONE) { + throw new VoucherException(FAIL_TO_DELETE_VOUCHER); + } + } + + @Transactional + public UUID update(UUID id, UpdateVoucherRequest request) { + Voucher exsistedVoucher = voucherRepository.findById(id) + .orElseThrow(() -> new VoucherException(NOT_FOUND_VOUCHER)); + + var updatedVoucher = new Voucher(id, request.getVoucherType(), request.getBenefitValue(), + timeGenerator.now()); + + if (voucherRepository.update(updatedVoucher) != AFFECTED_ROW_ONE) { + throw new VoucherException(FAIL_TO_UPDATE_VOUCHER); + } + return updatedVoucher.getId(); + } + + public List findByCriteria(VoucherCriteria voucherCriteria) { + return voucherRepository.findByCriteria(voucherCriteria).stream() + .map(VoucherResponse::of) + .toList(); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/ProdVoucherIdGenerator.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/ProdVoucherIdGenerator.java new file mode 100644 index 0000000000..6835ee6f0e --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/ProdVoucherIdGenerator.java @@ -0,0 +1,13 @@ +package com.programmers.springbootbasic.domain.voucher.domain; + +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class ProdVoucherIdGenerator implements VoucherIdGenerator { + + @Override + public UUID generate() { + return UUID.randomUUID(); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherIdGenerator.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherIdGenerator.java new file mode 100644 index 0000000000..bed16108ae --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherIdGenerator.java @@ -0,0 +1,7 @@ +package com.programmers.springbootbasic.domain.voucher.domain; + +import com.programmers.springbootbasic.common.IdGenerator; + +public interface VoucherIdGenerator extends IdGenerator { + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherRepository.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherRepository.java new file mode 100644 index 0000000000..d67d4b42ad --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherRepository.java @@ -0,0 +1,11 @@ +package com.programmers.springbootbasic.domain.voucher.domain; + +import com.programmers.springbootbasic.common.Repository; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import java.util.List; +import java.util.UUID; + +public interface VoucherRepository extends Repository { + List findByCriteria(VoucherCriteria criteria); +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/FixedAmountVoucher.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/FixedAmountVoucher.java new file mode 100644 index 0000000000..43a25d5dd1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/FixedAmountVoucher.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.domain.voucher.domain.VoucherType; + +import static com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum.FIXED; +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_FIXED_VOUCHER_BENEFIT; + +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +public class FixedAmountVoucher implements VoucherType { + + public FixedAmountVoucher(Integer benefit) { + if (benefit <= 0) { + throw new VoucherException(INVALID_FIXED_VOUCHER_BENEFIT); + } + } + + @Override + public String getVoucherTypeName() { + return FIXED.name(); + } + + @Override + public double getDiscountedPrice(double price, int benefitValue) { + return price - benefitValue; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/PercentDiscountVoucher.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/PercentDiscountVoucher.java new file mode 100644 index 0000000000..24cf5f62bb --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/PercentDiscountVoucher.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.domain.voucher.domain.VoucherType; + +import static com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum.PERCENT; +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_PERCENT_VOUCHER_BENEFIT; + +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +public class PercentDiscountVoucher implements VoucherType { + + public PercentDiscountVoucher(Integer benefit) { + if (benefit <= 0 || benefit > 100) { + throw new VoucherException(INVALID_PERCENT_VOUCHER_BENEFIT); + } + } + + @Override + public String getVoucherTypeName() { + return PERCENT.name(); + } + + @Override + public double getDiscountedPrice(double price, int benefitValue) { + return price * (100 - benefitValue) / 100; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/VoucherType.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/VoucherType.java new file mode 100644 index 0000000000..c2afa349c4 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/VoucherType.java @@ -0,0 +1,8 @@ +package com.programmers.springbootbasic.domain.voucher.domain.VoucherType; + +public interface VoucherType { + + String getVoucherTypeName(); + + double getDiscountedPrice(double price, int benefitValue); +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/VoucherTypeEnum.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/VoucherTypeEnum.java new file mode 100644 index 0000000000..b1ad1aab8b --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/VoucherTypeEnum.java @@ -0,0 +1,29 @@ +package com.programmers.springbootbasic.domain.voucher.domain.VoucherType; + +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_VOUCHER; + +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import java.util.function.Function; + +public enum VoucherTypeEnum { + FIXED(FixedAmountVoucher::new), + PERCENT(PercentDiscountVoucher::new); + + private final Function voucherType; + + VoucherTypeEnum(Function voucherType) { + this.voucherType = voucherType; + } + + public VoucherType getVoucherType(Integer benefit) { + return voucherType.apply(benefit); + } + + public static VoucherTypeEnum of(String voucherType) { + try { + return VoucherTypeEnum.valueOf(voucherType.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new VoucherException(INVALID_VOUCHER); + } + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/entity/Voucher.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/entity/Voucher.java new file mode 100644 index 0000000000..deaa7ca268 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/domain/entity/Voucher.java @@ -0,0 +1,56 @@ +package com.programmers.springbootbasic.domain.voucher.domain.entity; + + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherType; +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.UUID; + +public class Voucher { + + private final UUID id; + private final VoucherType voucherType; + private final Integer benefitValue; + private final LocalDateTime createdAt; + + public Voucher(UUID id, VoucherType voucherType, Integer benefitValue, LocalDateTime createdAt) { + this.id = id; + this.voucherType = voucherType; + this.benefitValue = benefitValue; + this.createdAt = createdAt; + } + + public UUID getId() { + return id; + } + + public VoucherType getVoucherType() { + return voucherType; + } + + public Integer getBenefitValue() { + return benefitValue; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + // 테스트코드를 위해 재정의 + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Voucher voucher)) { + return false; + } + return Objects.equals(getId(), voucher.getId()) && Objects.equals( + getVoucherType(), voucher.getVoucherType()) && Objects.equals(getBenefitValue(), + voucher.getBenefitValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getId(), getVoucherType(), getBenefitValue()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/FilePersistenceVoucherRepository.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/FilePersistenceVoucherRepository.java new file mode 100644 index 0000000000..ec0c8ed11d --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/FilePersistenceVoucherRepository.java @@ -0,0 +1,88 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure; + +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_FILE_PATH; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.CsvVoucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import com.programmers.springbootbasic.util.FileManager; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +@Repository +@Profile("dev") +@Primary +public class FilePersistenceVoucherRepository implements VoucherRepository { + + private final FileManager fileManager; + private final String fileName; + private final ConcurrentHashMap vouchers; + + public FilePersistenceVoucherRepository( + List fileManagerList, + @Value("${file.voucher.path}") String fileName + + ) { + this.fileName = fileName; + this.fileManager = initializeFileManager(fileManagerList, fileName); + this.vouchers = getCsvVoucherHashMap(); + } + + @Override + public Voucher save(Voucher voucher) { + fileManager.write(CsvVoucher.of(voucher), fileName, true); + return voucher; + } + + @Override + public List findAll() { + var csvVouchers = fileManager.read(fileName, CsvVoucher.class); + return csvVouchers.stream() + .map(CsvVoucher::toEntity) + .toList(); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(vouchers.get(id)); + } + + @Override + public int deleteById(UUID id) { + return vouchers.remove(id) == null ? 0 : 1; + } + + @Override + public int update(Voucher voucher) { + return vouchers.put(voucher.getId(), voucher) == null ? 0 : 1; + } + + private FileManager initializeFileManager(List fileManagerList, String fileName) { + return fileManagerList.stream() + .filter(fm -> fm.supports(fileName)) + .findFirst() + .orElseThrow(() -> new VoucherException(INVALID_FILE_PATH)); + } + + private ConcurrentHashMap getCsvVoucherHashMap() { + var csvVouchers = fileManager.read(fileName, CsvVoucher.class); + ConcurrentHashMap csvVoucherHashMap = new ConcurrentHashMap<>(); + csvVouchers.forEach( + csvVoucher -> csvVoucherHashMap.put(UUID.fromString(csvVoucher.getId()), + csvVoucher.toEntity())); + return csvVoucherHashMap; + } + + @Override + public List findByCriteria(VoucherCriteria criteria) { + return null; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/JdbcVoucherRepository.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/JdbcVoucherRepository.java new file mode 100644 index 0000000000..c8a11bb419 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/JdbcVoucherRepository.java @@ -0,0 +1,110 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import com.programmers.springbootbasic.util.SqlConverter; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcVoucherRepository implements VoucherRepository { + + private static final String TABLE_NAME = "vouchers"; + private static final String ID = "id"; + private static final String VOUCHER_TYPE = "voucher_type"; + private static final String BENEFIT_VALUE = "benefit_value"; + private static final String CREATED_AT = "created_at"; + + private final JdbcTemplate jdbcTemplate; + private final RowMapper voucherRowMapper = + (rs, rowNum) -> new Voucher( + UUID.fromString(rs.getString(ID)), + VoucherTypeEnum.of(rs.getString(VOUCHER_TYPE)) + .getVoucherType(rs.getInt(BENEFIT_VALUE)), + rs.getInt(BENEFIT_VALUE), + SqlConverter.toLocalDateTime(rs.getString(CREATED_AT)) + ); + + public JdbcVoucherRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Voucher save(Voucher voucher) { + jdbcTemplate.update( + String.format("INSERT INTO %s (%s, %s, %s, %s) VALUES (?, ?, ?, ?)", TABLE_NAME, ID, + VOUCHER_TYPE, BENEFIT_VALUE, CREATED_AT), + voucher.getId().toString(), + voucher.getVoucherType().getVoucherTypeName(), + voucher.getBenefitValue(), + voucher.getCreatedAt().toString()); + return voucher; + } + + @Override + public List findAll() { + return jdbcTemplate.query(String.format("SELECT * FROM %s", TABLE_NAME), voucherRowMapper); + } + + @Override + public Optional findById(UUID id) { + return jdbcTemplate.query(String.format("SELECT * FROM %s WHERE %s = ?", TABLE_NAME, ID), + voucherRowMapper, id.toString()) + .stream().findFirst(); + } + + @Override + public int deleteById(UUID id) { + return jdbcTemplate.update(String.format("DELETE FROM %s WHERE %s = ?", TABLE_NAME, ID), + id.toString()); + } + + @Override + public int update(Voucher voucher) { + return jdbcTemplate.update( + String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?", TABLE_NAME, VOUCHER_TYPE, + BENEFIT_VALUE, ID), + voucher.getVoucherType().getVoucherTypeName(), + voucher.getBenefitValue(), + voucher.getId().toString()); + } + + @Override + public List findByCriteria(VoucherCriteria criteria) { + String baseSql = String.format("SELECT * FROM %s", TABLE_NAME); + + if (criteria.getStartDate() != null && criteria.getEndDate() != null + && criteria.getType() == null) { + baseSql += String.format(" WHERE %s >= '%s' AND %s < '%s'", + CREATED_AT, + criteria.getStartDate(), + CREATED_AT, + criteria.getEndDate()); + } + + if (criteria.getType() != null && criteria.getStartDate() == null + && criteria.getEndDate() == null) { + baseSql += String.format(" WHERE %s = '%s'", VOUCHER_TYPE, criteria.getType()); + } + + if (criteria.getType() != null && criteria.getStartDate() != null + && criteria.getEndDate() != null) { + baseSql += String.format(" WHERE %s = '%s' AND %s >= '%s' AND %s < '%s'", + VOUCHER_TYPE, + criteria.getType(), + CREATED_AT, + criteria.getStartDate(), + CREATED_AT, + criteria.getEndDate()); + } + + return jdbcTemplate.query(baseSql, voucherRowMapper); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/MemoryVoucherRepository.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/MemoryVoucherRepository.java new file mode 100644 index 0000000000..e7dccbce5e --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/MemoryVoucherRepository.java @@ -0,0 +1,49 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +@Profile("local") +@Repository +public class MemoryVoucherRepository implements VoucherRepository { + + private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); + + @Override + public Voucher save(Voucher voucher) { + vouchers.put(voucher.getId(), voucher); + return voucher; + } + + @Override + public List findAll() { + return vouchers.values().stream().toList(); + } + + @Override + public Optional findById(UUID id) { + return Optional.ofNullable(vouchers.get(id)); + } + + @Override + public int deleteById(UUID id) { + return vouchers.remove(id) == null ? 0 : 1; + } + + @Override + public int update(Voucher voucher) { + return vouchers.put(voucher.getId(), voucher) == null ? 0 : 1; + } + + @Override + public List findByCriteria(VoucherCriteria criteria) { + return null; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/CsvVoucher.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/CsvVoucher.java new file mode 100644 index 0000000000..f2b470ebdc --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/CsvVoucher.java @@ -0,0 +1,51 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.util.SqlConverter; +import java.util.UUID; + +public class CsvVoucher { + + private String id; + private String voucherType; + private String benefitValue; + private String createdAt; + + public CsvVoucher() { + } + + public CsvVoucher(String id, String voucherType, String benefitValue, String createdAt) { + this.id = id; + this.voucherType = voucherType; + this.benefitValue = benefitValue; + this.createdAt = createdAt; + } + + public static CsvVoucher of(Voucher voucher) { + return new CsvVoucher(voucher.getId().toString(), + voucher.getVoucherType().getVoucherTypeName(), + voucher.getBenefitValue().toString(), + voucher.getCreatedAt().toString() + ); + } + + public Voucher toEntity() { + return new Voucher(UUID.fromString(id), + VoucherTypeEnum.of(voucherType).getVoucherType(Integer.valueOf(benefitValue)), + Integer.valueOf(benefitValue), + SqlConverter.toLocalDateTime(createdAt)); + } + + public String getId() { + return id; + } + + public String getVoucherType() { + return voucherType; + } + + public String getBenefitValue() { + return benefitValue; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/UserVoucherWalletWithUser.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/UserVoucherWalletWithUser.java new file mode 100644 index 0000000000..b0b583e3fe --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/UserVoucherWalletWithUser.java @@ -0,0 +1,22 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure.dto; + +import com.programmers.springbootbasic.domain.user.domain.entity.User; + +public class UserVoucherWalletWithUser { + + private final Long id; + private final User user; + + public UserVoucherWalletWithUser(Long id, User user) { + this.id = id; + this.user = user; + } + + public Long getId() { + return id; + } + + public User getUser() { + return user; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/UserVoucherWalletWithVoucher.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/UserVoucherWalletWithVoucher.java new file mode 100644 index 0000000000..37f1e9245f --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/infrastructure/dto/UserVoucherWalletWithVoucher.java @@ -0,0 +1,23 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; + +// TODO: 바뀔 여지가 있음 +public class UserVoucherWalletWithVoucher { + + private final Long id; + private final Voucher voucher; + + public UserVoucherWalletWithVoucher(Long id, Voucher voucher) { + this.id = id; + this.voucher = voucher; + } + + public Long getId() { + return id; + } + + public Voucher getVoucher() { + return voucher; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherController.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherController.java new file mode 100644 index 0000000000..5ab96e4b00 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherController.java @@ -0,0 +1,43 @@ +package com.programmers.springbootbasic.domain.voucher.presentation; + +import com.programmers.springbootbasic.domain.voucher.application.VoucherService; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.UpdateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; +import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Controller; +import org.springframework.validation.annotation.Validated; + +@Controller +@Validated +public class VoucherController { + + private final VoucherService voucherService; + + public VoucherController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + public void createVoucher(@Valid CreateVoucherRequest request) { + voucherService.create(request); + } + + public List getAllVouchers() { + return voucherService.findAll(); + } + + public VoucherResponse getVoucherById(UUID id) { + return voucherService.findById(id); + } + + public void deleteVoucherById(UUID id) { + voucherService.deleteById(id); + } + + public void updateVoucher(UUID id, @Valid UpdateVoucherRequest request) { + voucherService.update(id, request); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherRestController.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherRestController.java new file mode 100644 index 0000000000..36f29f8b3a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherRestController.java @@ -0,0 +1,64 @@ +package com.programmers.springbootbasic.domain.voucher.presentation; + +import com.programmers.springbootbasic.common.dto.BaseListResponse; +import com.programmers.springbootbasic.domain.voucher.application.VoucherService; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.UpdateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; +import jakarta.validation.Valid; +import java.util.UUID; +import org.springframework.context.annotation.Profile; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Profile(value = "tomcat") +@RestController +@RequestMapping("/api/voucher") +public class VoucherRestController { + + private final VoucherService voucherService; + + public VoucherRestController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @PostMapping + public UUID createVoucher(@Valid @RequestBody CreateVoucherRequest request) { + return voucherService.create(request); + } + + @GetMapping("/vouchers") + public BaseListResponse getAllVouchers() { + return BaseListResponse.from(voucherService.findAll()); + } + + @GetMapping("/vouchers/search") + public BaseListResponse getVouchersByCriteria( + @Valid @ModelAttribute VoucherCriteria criteria + ) { + return BaseListResponse.from(voucherService.findByCriteria(criteria)); + } + + @GetMapping("/{id}") + public VoucherResponse getVoucherById(@PathVariable UUID id) { + return voucherService.findById(id); + } + + @DeleteMapping("/{id}") + public void deleteVoucherById(@PathVariable UUID id) { + voucherService.deleteById(id); + } + + @PutMapping("/{id}") + public void updateVoucher(@PathVariable UUID id, @RequestBody UpdateVoucherRequest request) { + voucherService.update(id, request); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherThymeController.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherThymeController.java new file mode 100644 index 0000000000..ae15f2bf8a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/VoucherThymeController.java @@ -0,0 +1,83 @@ +package com.programmers.springbootbasic.domain.voucher.presentation; + +import com.programmers.springbootbasic.domain.voucher.application.VoucherService; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.UpdateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; +import jakarta.validation.Valid; +import java.util.List; +import java.util.UUID; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Profile(value = "tomcat") +@Controller +@RequestMapping("/voucher") +public class VoucherThymeController { + + private final VoucherService voucherService; + + public VoucherThymeController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping("/register") + public String getVoucherPage() { + return "voucher/register"; + } + + @PostMapping("/register") + public String createVoucher(@Valid @ModelAttribute CreateVoucherRequest request, Model model) { + voucherService.create(request); + model.addAttribute("voucherType", request.getVoucherType().getVoucherTypeName()); + model.addAttribute("benefit", request.getBenefitValue()); + + return "voucher/registrationComplete"; + } + + @GetMapping("/vouchers") + public String getAllVouchers(Model model) { + List vouchers = voucherService.findAll(); + model.addAttribute("vouchers", vouchers); + return "voucher/list"; + } + + @GetMapping("/{id}") + public String getVoucherDetails(@PathVariable UUID id, Model model) { + VoucherResponse voucher = voucherService.findById(id); + model.addAttribute("voucher", voucher); + return "voucher/details"; + } + + @GetMapping("/update/{id}") + public String getUpdateVoucherPage(@PathVariable UUID id, Model model) { + VoucherResponse voucher = voucherService.findById(id); + model.addAttribute("id", id); + return "voucher/update"; + } + + @PutMapping("/update/{id}") + public String updateVoucher( + @PathVariable UUID id, @ModelAttribute UpdateVoucherRequest request, Model model + ) { + voucherService.update(id, request); + model.addAttribute("voucherType", request.getVoucherType().getVoucherTypeName()); + model.addAttribute("benefit", request.getBenefitValue()); + return "voucher/updateComplete"; + } + + @DeleteMapping("/delete/{id}") + public String getDeleteVoucher(@PathVariable UUID id) { + voucherService.deleteById(id); + return "voucher/deleteComplete"; + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/CreateVoucherRequest.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/CreateVoucherRequest.java new file mode 100644 index 0000000000..140ee824cd --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/CreateVoucherRequest.java @@ -0,0 +1,38 @@ +package com.programmers.springbootbasic.domain.voucher.presentation.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherType; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import java.util.UUID; + +public class CreateVoucherRequest { + + @NotNull + private VoucherType voucherType; + @NotNull + private Integer benefitValue; + + private CreateVoucherRequest(VoucherTypeEnum voucherType, Integer benefitValue) { + this.voucherType = voucherType.getVoucherType(benefitValue); + this.benefitValue = benefitValue; + } + + // TODO: String 으로 받을지 Enum 으로 받을지는 고민해보자. + public static CreateVoucherRequest of(VoucherTypeEnum stringVoucherType, Integer benefitValue) { + return new CreateVoucherRequest(stringVoucherType, benefitValue); + } + + public Voucher toEntity(UUID id, LocalDateTime createdAt) { + return new Voucher(id, voucherType, benefitValue, createdAt); + } + + public VoucherType getVoucherType() { + return voucherType; + } + + public Integer getBenefitValue() { + return benefitValue; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/UpdateVoucherRequest.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/UpdateVoucherRequest.java new file mode 100644 index 0000000000..e144ba0694 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/UpdateVoucherRequest.java @@ -0,0 +1,26 @@ +package com.programmers.springbootbasic.domain.voucher.presentation.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherType; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import jakarta.annotation.Nullable; + +public class UpdateVoucherRequest { + @Nullable + private final VoucherType voucherType; + @Nullable + private final Integer benefitValue; + + public UpdateVoucherRequest(VoucherTypeEnum voucherType, Integer benefitValue) { + this.voucherType = voucherType.getVoucherType(benefitValue); + this.benefitValue = benefitValue; + } + + public VoucherType getVoucherType() { + return voucherType; + } + + public Integer getBenefitValue() { + return benefitValue; + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/VoucherCriteria.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/VoucherCriteria.java new file mode 100644 index 0000000000..c6af74b3b1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/VoucherCriteria.java @@ -0,0 +1,44 @@ +package com.programmers.springbootbasic.domain.voucher.presentation.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.PastOrPresent; +import java.time.LocalDate; +import org.springframework.format.annotation.DateTimeFormat; + +public class VoucherCriteria { + @PastOrPresent + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startDate; + @PastOrPresent + @Nullable + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endDate; + @Nullable + private VoucherTypeEnum type; + + public VoucherCriteria(LocalDate startDate, LocalDate endDate, VoucherTypeEnum type) { + this.startDate = startDate; + this.endDate = endDate; + this.type = type; + } + + @Nullable + public LocalDate getStartDate() { + return startDate; + } + + @Nullable + public LocalDate getEndDate() { + if (endDate == null) { + return null; + } + return endDate.plusDays(1); + } + + @Nullable + public VoucherTypeEnum getType() { + return type; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/VoucherResponse.java b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/VoucherResponse.java new file mode 100644 index 0000000000..a1ba7c876f --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/VoucherResponse.java @@ -0,0 +1,53 @@ +package com.programmers.springbootbasic.domain.voucher.presentation.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import java.time.LocalDateTime; +import java.util.UUID; + +public class VoucherResponse { + + private final UUID id; + private final String voucherType; + private final Integer benefitValue; + private final LocalDateTime createdAt; + + public VoucherResponse(UUID id, String voucherType, Integer benefitValue, + LocalDateTime createdAt + ) { + this.id = id; + this.voucherType = voucherType; + this.benefitValue = benefitValue; + this.createdAt = createdAt; + } + + public static VoucherResponse of(Voucher voucher) { + return new VoucherResponse(voucher.getId(), voucher.getVoucherType().getVoucherTypeName(), + voucher.getBenefitValue(), voucher.getCreatedAt()); + } + + @Override + public String toString() { + return """ + Voucher id = %s + Type = %s + benefitValue = %s + =============================== + """.formatted(id, voucherType, benefitValue); + } + + public UUID getId() { + return id; + } + + public String getVoucherType() { + return voucherType; + } + + public Integer getBenefitValue() { + return benefitValue; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/AppExceptionHandler.java b/src/main/java/com/programmers/springbootbasic/exception/AppExceptionHandler.java new file mode 100644 index 0000000000..dfafcbf20b --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/AppExceptionHandler.java @@ -0,0 +1,80 @@ +package com.programmers.springbootbasic.exception; + +import static com.programmers.springbootbasic.exception.ErrorCode.DATABASE_ERROR; + +import com.programmers.springbootbasic.exception.exceptionClass.MenuException; +import com.programmers.springbootbasic.exception.exceptionClass.FileIOException; +import com.programmers.springbootbasic.exception.exceptionClass.SystemException; +import com.programmers.springbootbasic.exception.exceptionClass.UserException; +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import com.programmers.springbootbasic.mediator.ConsoleResponse; +import com.programmers.springbootbasic.mediator.RequestProcessor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.dao.DataAccessException; +import org.springframework.stereotype.Component; + +@Component +public class AppExceptionHandler { + + private static final Logger logger = LoggerFactory.getLogger(AppExceptionHandler.class); + private final RequestProcessor requestProcessor; + private final ConfigurableApplicationContext context; + + public AppExceptionHandler( + RequestProcessor requestProcessor, + ConfigurableApplicationContext context + ) { + this.requestProcessor = requestProcessor; + this.context = context; + } + + public void handle() { + try { + requestProcessor.run(); + } catch (SystemException e) { + logger.error(String.format("System Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(e.getMessage()) + ); + handleExit(e); + } catch (FileIOException e) { + logger.error(String.format("FileIO Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(e.getMessage()) + ); + } catch (MenuException e) { + logger.error(String.format("Custom Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(e.getMessage()) + ); + } catch (VoucherException e) { + logger.error(String.format("Voucher Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(e.getMessage()) + ); + } catch (UserException e) { + logger.error(String.format("User Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(e.getMessage()) + ); + } catch (DataAccessException e) { + logger.error(String.format("DataAccess Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(DATABASE_ERROR.getMessage()) + ); + } catch (Exception e) { + logger.error(String.format("Unknown Error : %s", e.getMessage())); + requestProcessor.sendResponse( + ConsoleResponse.createNoBodyResponse(e.getMessage()) + ); + } + } + + private void handleExit(SystemException e) { + if (e.getErrorCode().equals(ErrorCode.EXIT)) { + context.close(); + } + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/ErrorCode.java b/src/main/java/com/programmers/springbootbasic/exception/ErrorCode.java new file mode 100644 index 0000000000..0c6ea854a4 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/ErrorCode.java @@ -0,0 +1,40 @@ +package com.programmers.springbootbasic.exception; + +import org.springframework.http.HttpStatus; + +public enum ErrorCode { + FILE_IO_ERROR("파일 에러입니다. 시스템을 종료합니다.", HttpStatus.INTERNAL_SERVER_ERROR), + DATABASE_ERROR("데이터베이스 에러입니다. 저장에 실패하였습니다. 다시 시도하세요.", HttpStatus.INTERNAL_SERVER_ERROR), + INVALID_MENU("올바르지 않은 메뉴 타입입니다.", HttpStatus.BAD_REQUEST), + INVALID_VOUCHER("올바르지 않은 바우처 타입 입니다. 현재 등록 가능한 바우처 타입은 FIXED, PERCENT 입니다.", HttpStatus.BAD_REQUEST), + INVALID_FIXED_VOUCHER_BENEFIT("고정 할인 금액은 0원 이상이어야 합니다.", HttpStatus.BAD_REQUEST), + INVALID_PERCENT_VOUCHER_BENEFIT("비율 할인 금액은 0% 이상 100% 이하여야 합니다.", HttpStatus.BAD_REQUEST), + INVALID_FILE_PATH("올바르지 않은 파일 경로입니다.", HttpStatus.INTERNAL_SERVER_ERROR), + INVALID_INPUT("올바르지 않은 입력입니다.", HttpStatus.BAD_REQUEST), + EXIT("시스템을 종료합니다.", HttpStatus.INTERNAL_SERVER_ERROR), + NOT_FOUND_VOUCHER("바우처를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), + NOT_FOUND_USER("사용자를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), + NOT_FOUND_USER_VOUCHER("사용자 바우처를 찾을 수 없습니다.", HttpStatus.BAD_REQUEST), + FAIL_TO_DELETE_VOUCHER("바우처 삭제에 실패하였습니다.", HttpStatus.INTERNAL_SERVER_ERROR), + FAIL_TO_UPDATE_VOUCHER("바우처 수정에 실패하였습니다.", HttpStatus.INTERNAL_SERVER_ERROR), + DUPLICATED_VOUCHER("이미 등록된 바우처 입니다.", HttpStatus.CONFLICT), + DUPLICATED_USER("이미 등록된 사용자 입니다.", HttpStatus.CONFLICT), + FAIL_TO_DELETE_USER_VOUCHER("사용자 바우처 삭제에 실패하였습니다.", HttpStatus.INTERNAL_SERVER_ERROR), + ; + + ErrorCode(String message, HttpStatus httpStatus) { + this.message = message; + this.httpStatus = httpStatus; + } + + private final String message; + private final HttpStatus httpStatus; + + public String getMessage() { + return message; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/ErrorResponse.java b/src/main/java/com/programmers/springbootbasic/exception/ErrorResponse.java new file mode 100644 index 0000000000..4f5f40e38a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/ErrorResponse.java @@ -0,0 +1,29 @@ +package com.programmers.springbootbasic.exception; + +import lombok.Builder; + +public class ErrorResponse { + + private final String message; + private final int status; + private final String code; + + @Builder + private ErrorResponse(String message, int status, String code) { + this.message = message; + this.status = status; + this.code = code; + } + + public String getMessage() { + return message; + } + + public int getStatus() { + return status; + } + + public String getCode() { + return code; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/GlobalRestExceptionHandler.java b/src/main/java/com/programmers/springbootbasic/exception/GlobalRestExceptionHandler.java new file mode 100644 index 0000000000..7c73f84ad8 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/GlobalRestExceptionHandler.java @@ -0,0 +1,62 @@ +package com.programmers.springbootbasic.exception; + +import com.programmers.springbootbasic.domain.voucher.presentation.VoucherRestController; +import com.programmers.springbootbasic.exception.exceptionClass.CustomException; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice(assignableTypes = VoucherRestController.class) +public class GlobalRestExceptionHandler { + + @ExceptionHandler({ + CustomException.class, + }) + public ResponseEntity handleUserException(CustomException ex) { + return ResponseEntity + .status(ex.getErrorCode().getHttpStatus()) + .body(ErrorResponse.builder() + .message(ex.getMessage()) + .status(ex.getErrorCode().getHttpStatus().value()) + .code(ex.getErrorCode().name()) + .build()); + } + + @ExceptionHandler(value = { + BindException.class, + MethodArgumentNotValidException.class + }) + public ResponseEntity validationError(BindException e) { + BindingResult bindingResult = e.getBindingResult(); + + StringBuilder builder = new StringBuilder(); + for (FieldError fieldError : bindingResult.getFieldErrors()) { + builder.append("["); + builder.append(fieldError.getField()); + builder.append("](은)는 "); + builder.append(fieldError.getDefaultMessage()); + builder.append(" 입력된 값: ["); + builder.append(fieldError.getRejectedValue()); + builder.append("]\n"); + } + return ResponseEntity.badRequest().body(ErrorResponse.builder() + .message(builder.toString()) + .status(400) + .code(ErrorCode.INVALID_INPUT.name()) + .build()); + } + + @ExceptionHandler({ + Exception.class, + }) + public ResponseEntity handleException(Exception ex) { + return ResponseEntity + .internalServerError() + .body(ex.getMessage()); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/GlobalThymeExceptionHandler.java b/src/main/java/com/programmers/springbootbasic/exception/GlobalThymeExceptionHandler.java new file mode 100644 index 0000000000..cac011428d --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/GlobalThymeExceptionHandler.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.exception; + +import com.programmers.springbootbasic.exception.exceptionClass.CustomException; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalThymeExceptionHandler { + + @ExceptionHandler({ + CustomException.class, + }) + public String handleUserException(CustomException ex, Model model) { + System.out.println("GlobalThymeExceptionHandler.handleUserException"); + model.addAttribute("error", ex.getErrorCode().getHttpStatus()); + model.addAttribute("message", ex.getMessage()); + return "customError"; + } + + @ExceptionHandler({ + Exception.class, + }) + public String handleException() { + return "defaultError"; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/NoMappingThymeExceptionHandler.java b/src/main/java/com/programmers/springbootbasic/exception/NoMappingThymeExceptionHandler.java new file mode 100644 index 0000000000..e452927457 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/NoMappingThymeExceptionHandler.java @@ -0,0 +1,13 @@ +package com.programmers.springbootbasic.exception; + +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class NoMappingThymeExceptionHandler implements ErrorController { + @RequestMapping("/error") + public String handleError() { + return "defaultError"; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/CustomException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/CustomException.java new file mode 100644 index 0000000000..694b18183e --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/CustomException.java @@ -0,0 +1,16 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class CustomException extends RuntimeException { + private final ErrorCode errorCode; + + public CustomException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/FileIOException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/FileIOException.java new file mode 100644 index 0000000000..b2d6166e05 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/FileIOException.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class FileIOException extends CustomException { + public FileIOException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/MenuException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/MenuException.java new file mode 100644 index 0000000000..a29be16f5e --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/MenuException.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class MenuException extends CustomException { + public MenuException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/SystemException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/SystemException.java new file mode 100644 index 0000000000..1abcee0f75 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/SystemException.java @@ -0,0 +1,10 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class SystemException extends CustomException { + + public SystemException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/UserException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/UserException.java new file mode 100644 index 0000000000..ab4ff2000c --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/UserException.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class UserException extends CustomException { + public UserException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/UserVoucherWalletException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/UserVoucherWalletException.java new file mode 100644 index 0000000000..b7ffc1789c --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/UserVoucherWalletException.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class UserVoucherWalletException extends CustomException { + public UserVoucherWalletException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/VoucherException.java b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/VoucherException.java new file mode 100644 index 0000000000..e2b8c850ff --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/exception/exceptionClass/VoucherException.java @@ -0,0 +1,9 @@ +package com.programmers.springbootbasic.exception.exceptionClass; + +import com.programmers.springbootbasic.exception.ErrorCode; + +public class VoucherException extends CustomException { + public VoucherException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/infrastructure/IO/Console.java b/src/main/java/com/programmers/springbootbasic/infrastructure/IO/Console.java new file mode 100644 index 0000000000..1509604a33 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/infrastructure/IO/Console.java @@ -0,0 +1,88 @@ +package com.programmers.springbootbasic.infrastructure.IO; + +import com.programmers.springbootbasic.presentation.IOManager; +import com.programmers.springbootbasic.presentation.validation.InputValidator; +import java.util.Scanner; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +@Component +public class Console implements IOManager { + + private final Scanner scanner; + private final InputValidator stringValidator; + private final InputValidator integerValidator; + private final InputValidator longValidator; + private final InputValidator uuidValidator; + + public Console( + @Qualifier("getStringValidator") InputValidator stringValidator, + @Qualifier("getIntegerValidator") InputValidator integerValidator, + @Qualifier("getLongValidator") InputValidator longValidator, + @Qualifier("getUUIDValidator") InputValidator uuidValidator + ) { + this.longValidator = longValidator; + this.uuidValidator = uuidValidator; + this.scanner = new Scanner(System.in); + this.stringValidator = stringValidator; + this.integerValidator = integerValidator; + } + + public String collectStringInput(String message) { + while (true) { + print(message); + var result = scanner.nextLine().trim(); + var errors = stringValidator.validate(result); + + if (errors.isEmpty()) { + return result; + } + errors.get().forEach(this::print); + } + } + + public int collectIntegerInput(String message) { + while (true) { + print(message); + var result = scanner.nextLine().trim(); + var errors = integerValidator.validate(result); + + if (errors.isEmpty()) { + return Integer.parseInt(result); + } + errors.get().forEach(this::print); + } + } + + public Long collectLongInput(String message) { + while (true) { + print(message); + var result = scanner.nextLine().trim(); + var errors = longValidator.validate(result); + + if (errors.isEmpty()) { + return Long.parseLong(result); + } + errors.get().forEach(this::print); + } + } + + public UUID collectUUIDInput(String message) { + while (true) { + print(message); + var result = scanner.nextLine().trim(); + var errors = uuidValidator.validate(result); + + if (errors.isEmpty()) { + return UUID.fromString(result); + } + errors.get().forEach(this::print); + } + } + + public void print(String message) { + System.out.println(message); + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/infrastructure/IO/ConsoleInteractionAggregator.java b/src/main/java/com/programmers/springbootbasic/infrastructure/IO/ConsoleInteractionAggregator.java new file mode 100644 index 0000000000..24e193d116 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/infrastructure/IO/ConsoleInteractionAggregator.java @@ -0,0 +1,66 @@ +package com.programmers.springbootbasic.infrastructure.IO; + +import com.programmers.springbootbasic.domain.user.presentation.dto.CreateUserRequest; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.mediator.dto.UpdateVoucherMediatorRequest; +import com.programmers.springbootbasic.util.Messages; +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class ConsoleInteractionAggregator { + + private final Console console; + + public ConsoleInteractionAggregator(Console console) { + this.console = console; + } + + public String collectMenuInput() { + return console.collectStringInput(Messages.SELECT_MENU.getMessage()); + } + + public CreateVoucherRequest collectVoucherInput() { + String type = console.collectStringInput(Messages.VOUCHER_REGISTER_TYPE.getMessage()); + int amount = console.collectIntegerInput(Messages.VOUCHER_REGISTER_AMOUNT.getMessage()); + return CreateVoucherRequest.of(VoucherTypeEnum.of(type), amount); + } + + public CreateUserRequest collectUserInput() { + String nickname = console.collectStringInput(Messages.INPUT_NICKNAME.getMessage()); + return CreateUserRequest.of(nickname); + } + + //TODO: 메세지 어찌할지 + public Long collectIdInput() { + return console.collectLongInput(Messages.INPUT_ID.getMessage()); + } + + public UUID collectUUIDInput() { + return console.collectUUIDInput(Messages.INPUT_ID.getMessage()); + } + + public UpdateVoucherMediatorRequest collectUpdateVoucherInput() { + UUID id = console.collectUUIDInput(Messages.INPUT_ID.getMessage()); + String voucherType = console.collectStringInput(Messages.VOUCHER_UPDATE_TYPE.getMessage()); + int amount = console.collectIntegerInput(Messages.VOUCHER_UPDATE_AMOUNT.getMessage()); + return UpdateVoucherMediatorRequest.of(id, voucherType, amount); + } + + public CreateUserVoucherWalletRequest collectUserVoucherWalletInput() { + String userNickname = console.collectStringInput(Messages.INPUT_NICKNAME.getMessage()); + UUID voucherId = console.collectUUIDInput(Messages.INPUT_ID.getMessage()); + return CreateUserVoucherWalletRequest.of(userNickname, voucherId); + } + + public String collectNicknameInput() { + return console.collectStringInput(Messages.INPUT_NICKNAME.getMessage()); + } + + + public void displayMessage(String message) { + console.print(message); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/infrastructure/IO/ConsoleValidatorConfig.java b/src/main/java/com/programmers/springbootbasic/infrastructure/IO/ConsoleValidatorConfig.java new file mode 100644 index 0000000000..2c6cc2a663 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/infrastructure/IO/ConsoleValidatorConfig.java @@ -0,0 +1,76 @@ +package com.programmers.springbootbasic.infrastructure.IO; + +import com.programmers.springbootbasic.presentation.validation.InputValidator; +import java.util.UUID; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ConsoleValidatorConfig { + + @Bean + public InputValidator getStringValidator() { + InputValidator stringValidator = new InputValidator<>(); + stringValidator.addValidator(this::validateString); + return stringValidator; + } + + @Bean + public InputValidator getIntegerValidator() { + InputValidator integerValidator = new InputValidator<>(); + integerValidator.addValidator(this::validateString); + integerValidator.addValidator(this::validateInteger); + return integerValidator; + } + + @Bean + public InputValidator getLongValidator() { + InputValidator longValidator = new InputValidator<>(); + longValidator.addValidator(this::validateString); + longValidator.addValidator(this::validateInteger); + return longValidator; + } + + @Bean + public InputValidator getUUIDValidator() { + InputValidator uuidValidator = new InputValidator<>(); + uuidValidator.addValidator(this::validateString); + uuidValidator.addValidator(this::validateUUID); + return uuidValidator; + } + + private String validateString(String input) { + if (input == null || input.isEmpty()) { + return "입력 값이 없습니다."; + } + return null; + } + + private String validateInteger(String input) { + try { + Integer.parseInt(input); + return null; + } catch (NumberFormatException e) { + return "숫자가 아닙니다."; + } + } + + private String validateLong(String input) { + try { + Long.parseLong(input); + return null; + } catch (NumberFormatException e) { + return "Long 형태의 숫자가 아닙니다."; + } + } + + private String validateUUID(String input) { + try { + UUID.fromString(input); + return null; + } catch (IllegalArgumentException e) { + return "UUID 형태의 문자가 아닙니다."; + } + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/ConsoleRequest.java b/src/main/java/com/programmers/springbootbasic/mediator/ConsoleRequest.java new file mode 100644 index 0000000000..0d5dff2698 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/ConsoleRequest.java @@ -0,0 +1,26 @@ +package com.programmers.springbootbasic.mediator; + +import java.util.Optional; + +public class ConsoleRequest { + + private final String command; + private final T body; + + public ConsoleRequest(String command) { + this(command, null); + } + + public ConsoleRequest(String command, T body) { + this.command = command; + this.body = body; + } + + public Optional getBody() { + return Optional.ofNullable(body); + } + + public String getCommand() { + return command; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/ConsoleResponse.java b/src/main/java/com/programmers/springbootbasic/mediator/ConsoleResponse.java new file mode 100644 index 0000000000..41d89c40b6 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/ConsoleResponse.java @@ -0,0 +1,34 @@ +package com.programmers.springbootbasic.mediator; + +import java.util.Optional; + +public class ConsoleResponse { + + private final String message; + private final T body; + + private ConsoleResponse(String message) { + this(null, message); + } + + private ConsoleResponse(T body, String message) { + this.body = body; + this.message = message; + } + + public Optional getMessage() { + return Optional.ofNullable(this.message); + } + + public Optional getBody() { + return Optional.ofNullable(this.body); + } + + public static ConsoleResponse createNoBodyResponse(String message) { + return new ConsoleResponse<>(message); + } + + public static ConsoleResponse createWithBodyResponse(T body, String message) { + return new ConsoleResponse<>(body, message); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/RequestProcessor.java b/src/main/java/com/programmers/springbootbasic/mediator/RequestProcessor.java new file mode 100644 index 0000000000..6a768ffa81 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/RequestProcessor.java @@ -0,0 +1,60 @@ +package com.programmers.springbootbasic.mediator; + +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.provider.MenuRequestProvider; +import com.programmers.springbootbasic.presentation.ControllerAdapter; +import com.programmers.springbootbasic.presentation.MainMenu; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class RequestProcessor { + + private final ControllerAdapter controllerAdapter; + private final MenuRequestProvider menuRequestProvider; + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public RequestProcessor( + ControllerAdapter controllerAdapter, + MenuRequestProvider menuRequestProvider, + ConsoleInteractionAggregator consoleInteractionAggregator + ) { + this.controllerAdapter = controllerAdapter; + this.menuRequestProvider = menuRequestProvider; + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + public void run() { + ConsoleRequest request = getRequest(); + ConsoleResponse response = process(request); + sendResponse(response); + } + + public ConsoleRequest getRequest() { + var menuInput = consoleInteractionAggregator.collectMenuInput(); + + return menuRequestProvider.getMenuRequest(menuInput); + } + + public ConsoleResponse process(ConsoleRequest request) { + return MainMenu.routeToController(request, controllerAdapter); + } + + public void sendResponse(ConsoleResponse response) { + response.getBody().ifPresent(body -> { + if (body instanceof List listBody) { + listBody.forEach( + item -> consoleInteractionAggregator.displayMessage(item.toString())); + } else { + consoleInteractionAggregator.displayMessage(body.toString()); + } + }); + + response.getMessage().ifPresent( + message -> consoleInteractionAggregator.displayMessage( + (String) message) + ); + + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/dto/UpdateVoucherMediatorRequest.java b/src/main/java/com/programmers/springbootbasic/mediator/dto/UpdateVoucherMediatorRequest.java new file mode 100644 index 0000000000..e55cf6c823 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/dto/UpdateVoucherMediatorRequest.java @@ -0,0 +1,42 @@ +package com.programmers.springbootbasic.mediator.dto; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.UpdateVoucherRequest; +import java.util.UUID; + +public class UpdateVoucherMediatorRequest { + + private final UUID id; + private final VoucherTypeEnum voucherType; + private final Integer benefitValue; + + public UpdateVoucherMediatorRequest( + UUID id, VoucherTypeEnum voucherType, Integer benefitValue + ) { + this.id = id; + this.voucherType = voucherType; + this.benefitValue = benefitValue; + } + + public static UpdateVoucherMediatorRequest of( + UUID id, String voucherType, Integer benefitValue + ) { + return new UpdateVoucherMediatorRequest(id, VoucherTypeEnum.of(voucherType), benefitValue); + } + + public UpdateVoucherRequest toUpdateVoucherRequest() { + return new UpdateVoucherRequest(voucherType, benefitValue); + } + + public UUID getId() { + return id; + } + + public VoucherTypeEnum getVoucherType() { + return voucherType; + } + + public Integer getBenefitValue() { + return benefitValue; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/provider/MenuRequestProvider.java b/src/main/java/com/programmers/springbootbasic/mediator/provider/MenuRequestProvider.java new file mode 100644 index 0000000000..96d955e29b --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/provider/MenuRequestProvider.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.mediator.provider; + +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_MENU; + +import com.programmers.springbootbasic.exception.exceptionClass.MenuException; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.mediator.requestGenerator.MenuRequestGenerator; +import java.util.List; +import org.springframework.stereotype.Component; + +@Component +public class MenuRequestProvider { + + List menuRequestGenerators; + + public MenuRequestProvider(List menuRequestGenerators) { + this.menuRequestGenerators = menuRequestGenerators; + } + + public ConsoleRequest getMenuRequest(String menuName) { + return menuRequestGenerators.stream() + .filter(menuRequestGenerator -> menuRequestGenerator.getMenuCommand().equals(menuName)) + .findFirst() + .map(MenuRequestGenerator::generateRequest) + .orElseThrow(() -> new MenuException(INVALID_MENU)); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/DeleteMyVoucher.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/DeleteMyVoucher.java new file mode 100644 index 0000000000..13c877919e --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/DeleteMyVoucher.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class DeleteMyVoucher implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public DeleteMyVoucher(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.DELETE_VOUCHER_MINE.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), + consoleInteractionAggregator.collectIdInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/DeleteVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/DeleteVoucherGenerator.java new file mode 100644 index 0000000000..9b792057f9 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/DeleteVoucherGenerator.java @@ -0,0 +1,28 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class DeleteVoucherGenerator implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public DeleteVoucherGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.DELETE_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), + consoleInteractionAggregator.collectUUIDInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ExitRequestGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ExitRequestGenerator.java new file mode 100644 index 0000000000..5aea38933c --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ExitRequestGenerator.java @@ -0,0 +1,19 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class ExitRequestGenerator implements MenuRequestGenerator { + + @Override + public String getMenuCommand() { + return MainMenu.EXIT.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindMyVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindMyVoucherGenerator.java new file mode 100644 index 0000000000..0f8716d7db --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindMyVoucherGenerator.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class FindMyVoucherGenerator implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public FindMyVoucherGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.FIND_VOUCHER_MINE.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), + consoleInteractionAggregator.collectNicknameInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindUserByVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindUserByVoucherGenerator.java new file mode 100644 index 0000000000..759a74a78a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindUserByVoucherGenerator.java @@ -0,0 +1,28 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class FindUserByVoucherGenerator implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public FindUserByVoucherGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.FIND_USER_BY_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), + consoleInteractionAggregator.collectUUIDInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindVoucherByIdGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindVoucherByIdGenerator.java new file mode 100644 index 0000000000..aebdbc6020 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/FindVoucherByIdGenerator.java @@ -0,0 +1,19 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class FindVoucherByIdGenerator implements MenuRequestGenerator { + + @Override + public String getMenuCommand() { + return MainMenu.FIND_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/MenuRequestGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/MenuRequestGenerator.java new file mode 100644 index 0000000000..b920367182 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/MenuRequestGenerator.java @@ -0,0 +1,10 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.mediator.ConsoleRequest; + +public interface MenuRequestGenerator { + + String getMenuCommand(); + + ConsoleRequest generateRequest(); +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterUserGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterUserGenerator.java new file mode 100644 index 0000000000..99e3431ac8 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterUserGenerator.java @@ -0,0 +1,27 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.domain.user.presentation.dto.CreateUserRequest; +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class RegisterUserGenerator implements MenuRequestGenerator{ + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public RegisterUserGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.CREATE_USER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), consoleInteractionAggregator.collectUserInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterUserVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterUserVoucherGenerator.java new file mode 100644 index 0000000000..aa27d4ceae --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterUserVoucherGenerator.java @@ -0,0 +1,28 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class RegisterUserVoucherGenerator implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public RegisterUserVoucherGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.CREATE_USER_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), + consoleInteractionAggregator.collectUserVoucherWalletInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterVoucherGenerator.java new file mode 100644 index 0000000000..7c5a62e59d --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/RegisterVoucherGenerator.java @@ -0,0 +1,28 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class RegisterVoucherGenerator implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public RegisterVoucherGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.CREATE_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand(), + consoleInteractionAggregator.collectVoucherInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/UpdateVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/UpdateVoucherGenerator.java new file mode 100644 index 0000000000..783e1198b4 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/UpdateVoucherGenerator.java @@ -0,0 +1,28 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.infrastructure.IO.ConsoleInteractionAggregator; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.mediator.dto.UpdateVoucherMediatorRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class UpdateVoucherGenerator implements MenuRequestGenerator { + + private final ConsoleInteractionAggregator consoleInteractionAggregator; + + public UpdateVoucherGenerator(ConsoleInteractionAggregator consoleInteractionAggregator) { + this.consoleInteractionAggregator = consoleInteractionAggregator; + } + + @Override + public String getMenuCommand() { + return MainMenu.UPDATE_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest(getMenuCommand(), + consoleInteractionAggregator.collectUpdateVoucherInput()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ViewAllBlackUserGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ViewAllBlackUserGenerator.java new file mode 100644 index 0000000000..7325375b38 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ViewAllBlackUserGenerator.java @@ -0,0 +1,19 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class ViewAllBlackUserGenerator implements MenuRequestGenerator { + + @Override + public String getMenuCommand() { + return MainMenu.LIST_BLACK_USER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ViewAllVoucherGenerator.java b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ViewAllVoucherGenerator.java new file mode 100644 index 0000000000..1f63e3675a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/mediator/requestGenerator/ViewAllVoucherGenerator.java @@ -0,0 +1,19 @@ +package com.programmers.springbootbasic.mediator.requestGenerator; + +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.presentation.MainMenu; +import org.springframework.stereotype.Component; + +@Component +public class ViewAllVoucherGenerator implements MenuRequestGenerator { + + @Override + public String getMenuCommand() { + return MainMenu.FIND_ALL_VOUCHER.getCommand(); + } + + @Override + public ConsoleRequest generateRequest() { + return new ConsoleRequest<>(getMenuCommand()); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/presentation/ControllerAdapter.java b/src/main/java/com/programmers/springbootbasic/presentation/ControllerAdapter.java new file mode 100644 index 0000000000..245320645a --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/presentation/ControllerAdapter.java @@ -0,0 +1,123 @@ +package com.programmers.springbootbasic.presentation; + +import static com.programmers.springbootbasic.exception.ErrorCode.EXIT; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_BLACK_USER_LIST; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_USER_FOUND_BY_VOUCHER; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_USER_REGISTER; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_USER_VOUCHER_DELETE; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_USER_VOUCHER_FOUND_MINE; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_USER_VOUCHER_REGISTER; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_VOUCHER_DELETE; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_VOUCHER_FOUND; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_VOUCHER_LIST; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_VOUCHER_REGISTER; +import static com.programmers.springbootbasic.util.Messages.SUCCESS_VOUCHER_UPDATE; + +import com.programmers.springbootbasic.domain.user.presentation.UserController; +import com.programmers.springbootbasic.domain.user.presentation.dto.CreateUserRequest; +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.UserVoucherWalletController; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.UserOwnedVoucherResponse; +import com.programmers.springbootbasic.domain.voucher.presentation.VoucherController; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; +import com.programmers.springbootbasic.exception.exceptionClass.SystemException; +import com.programmers.springbootbasic.mediator.ConsoleResponse; +import com.programmers.springbootbasic.mediator.dto.UpdateVoucherMediatorRequest; +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class ControllerAdapter { + + private final VoucherController voucherController; + private final UserController userController; + private final UserVoucherWalletController userVoucherWalletController; + + public ControllerAdapter( + VoucherController voucherController, UserController userController, + UserVoucherWalletController userVoucherWalletController + ) { + this.voucherController = voucherController; + this.userController = userController; + this.userVoucherWalletController = userVoucherWalletController; + } + + public ConsoleResponse handleExit(Object... params) { + throw new SystemException(EXIT); + } + + public ConsoleResponse createVoucher(Object... params) { + CreateVoucherRequest request = (CreateVoucherRequest) params[0]; + voucherController.createVoucher(request); + return ConsoleResponse.createNoBodyResponse(SUCCESS_VOUCHER_REGISTER.getMessage()); + } + + public ConsoleResponse> getAllVouchers(Object... params) { + return ConsoleResponse.createWithBodyResponse(voucherController.getAllVouchers(), + SUCCESS_VOUCHER_LIST.getMessage()); + } + + public ConsoleResponse getVoucherById(Object... params) { + UUID id = (UUID) params[0]; + return ConsoleResponse.createWithBodyResponse( + voucherController.getVoucherById(id), + SUCCESS_VOUCHER_FOUND.getMessage()); + } + + public ConsoleResponse deleteVoucherById(Object... params) { + UUID id = (UUID) params[0]; + voucherController.deleteVoucherById(id); + return ConsoleResponse.createNoBodyResponse(SUCCESS_VOUCHER_DELETE.getMessage()); + } + + public ConsoleResponse updateVoucher(Object... params) { + UpdateVoucherMediatorRequest request = (UpdateVoucherMediatorRequest) params[0]; + + voucherController.updateVoucher(request.getId(), request.toUpdateVoucherRequest()); + return ConsoleResponse.createNoBodyResponse(SUCCESS_VOUCHER_UPDATE.getMessage()); + } + + public ConsoleResponse createUser(Object... params) { + CreateUserRequest request = (CreateUserRequest) params[0]; + userController.createUser(request); + return ConsoleResponse.createNoBodyResponse(SUCCESS_USER_REGISTER.getMessage()); + } + + public ConsoleResponse> getBlackList(Object... params) { + return ConsoleResponse.createWithBodyResponse(userController.getBlackList(), + SUCCESS_BLACK_USER_LIST.getMessage()); + } + + public ConsoleResponse createUserVoucher(Object... params) { + CreateUserVoucherWalletRequest request = (CreateUserVoucherWalletRequest) params[0]; + userVoucherWalletController.createUserVoucher(request); + return ConsoleResponse.createNoBodyResponse(SUCCESS_USER_VOUCHER_REGISTER.getMessage()); + } + + public ConsoleResponse> findUserByVoucherId(Object... params) { + UUID voucherId = (UUID) params[0]; + return ConsoleResponse.createWithBodyResponse( + userVoucherWalletController.findUserByVoucherId(voucherId), + SUCCESS_USER_FOUND_BY_VOUCHER.getMessage()); + } + + public ConsoleResponse> findVoucherByUserNickname( + Object... params + ) { + String nickname = (String) params[0]; + return ConsoleResponse.createWithBodyResponse( + userVoucherWalletController.findVoucherByUserNickname(nickname), + SUCCESS_USER_VOUCHER_FOUND_MINE.getMessage()); + } + + public ConsoleResponse deleteUserVoucherById(Object... params) { + Long id = (Long) params[0]; + userVoucherWalletController.deleteUserVoucher(id); + return ConsoleResponse.createNoBodyResponse(SUCCESS_USER_VOUCHER_DELETE.getMessage()); + } + + +} diff --git a/src/main/java/com/programmers/springbootbasic/presentation/IOManager.java b/src/main/java/com/programmers/springbootbasic/presentation/IOManager.java new file mode 100644 index 0000000000..9c8c835d43 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/presentation/IOManager.java @@ -0,0 +1,8 @@ +package com.programmers.springbootbasic.presentation; + +public interface IOManager { + + String collectStringInput(String message); + + void print(String message); +} diff --git a/src/main/java/com/programmers/springbootbasic/presentation/MainMenu.java b/src/main/java/com/programmers/springbootbasic/presentation/MainMenu.java new file mode 100644 index 0000000000..a9825404e1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/presentation/MainMenu.java @@ -0,0 +1,58 @@ +package com.programmers.springbootbasic.presentation; + +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_MENU; + +import com.programmers.springbootbasic.exception.exceptionClass.MenuException; +import com.programmers.springbootbasic.mediator.ConsoleRequest; +import com.programmers.springbootbasic.mediator.ConsoleResponse; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +public enum MainMenu { + EXIT("exit", ControllerAdapter::handleExit), + CREATE_VOUCHER("create voucher", ControllerAdapter::createVoucher), + FIND_ALL_VOUCHER("list voucher", ControllerAdapter::getAllVouchers), + FIND_VOUCHER("find voucher", ControllerAdapter::getVoucherById), + DELETE_VOUCHER("delete voucher", ControllerAdapter::deleteVoucherById), + UPDATE_VOUCHER("update voucher", ControllerAdapter::updateVoucher), + CREATE_USER("register user", ControllerAdapter::createUser), + LIST_BLACK_USER("list black user", ControllerAdapter::getBlackList), + CREATE_USER_VOUCHER("register my voucher", ControllerAdapter::createUserVoucher), + FIND_USER_BY_VOUCHER("find users by voucher Id", ControllerAdapter::findUserByVoucherId), + FIND_VOUCHER_MINE("find my voucher", ControllerAdapter::findVoucherByUserNickname), + DELETE_VOUCHER_MINE("delete my voucher", ControllerAdapter::deleteUserVoucherById), + ; + + private final String command; + private final BiFunction function; + + MainMenu(String command, BiFunction function) { + this.command = command; + this.function = function; + } + + public String getCommand() { + return command; + } + + public ConsoleResponse execute(ControllerAdapter controller, Object... params) { + return function.apply(controller, params); + } + + public static ConsoleResponse routeToController( + ConsoleRequest req, + ControllerAdapter controllerAdapter + ) { + return Stream.of(values()) + .filter(menuCommand -> menuCommand.getCommand().equals(req.getCommand())) + .findFirst() + .map(menuCommand -> { + if (req.getBody().isPresent()) { + return menuCommand.execute(controllerAdapter, req.getBody().get()); + } else { + return menuCommand.execute(controllerAdapter); + } + }) + .orElseThrow(() -> new MenuException(INVALID_MENU)); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/presentation/validation/InputValidator.java b/src/main/java/com/programmers/springbootbasic/presentation/validation/InputValidator.java new file mode 100644 index 0000000000..87baf0d2a5 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/presentation/validation/InputValidator.java @@ -0,0 +1,38 @@ +package com.programmers.springbootbasic.presentation.validation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class InputValidator { + + protected final List> validators = new ArrayList<>(); + + @FunctionalInterface + public interface Validator { + + String getErrorMessage(T val); + } + + public Optional> validate(T input) { + var errs = getError(input); + if (errs.isEmpty()) { + return Optional.empty(); + } + return Optional.of(errs); + } + + public void addValidator(Validator validator) { + if (validator != null) { + this.validators.add(validator); + } + } + + private List getError(T input) { + return validators.stream() + .map(validator -> validator.getErrorMessage(input)) + .filter(Objects::nonNull) + .toList(); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/thyme/HomeController.java b/src/main/java/com/programmers/springbootbasic/thyme/HomeController.java new file mode 100644 index 0000000000..e9a3f0e28d --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/thyme/HomeController.java @@ -0,0 +1,16 @@ +package com.programmers.springbootbasic.thyme; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("") +@Controller +public class HomeController { + + @GetMapping("") + public String home() { + return "home"; + } + +} diff --git a/src/main/java/com/programmers/springbootbasic/util/CsvManager.java b/src/main/java/com/programmers/springbootbasic/util/CsvManager.java new file mode 100644 index 0000000000..259edf9c6c --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/util/CsvManager.java @@ -0,0 +1,124 @@ +package com.programmers.springbootbasic.util; + + +import static com.programmers.springbootbasic.exception.ErrorCode.FILE_IO_ERROR; + +import com.programmers.springbootbasic.exception.exceptionClass.FileIOException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.springframework.core.io.PathResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Component; + +@Component +public class CsvManager implements FileManager { + + private static final Set EXTENSION = Set.of("csv"); + + @Override + public boolean supports(String fileName) { + String extension = fileName.substring(fileName.lastIndexOf(".") + 1); + return EXTENSION.contains(extension); + } + + @Override + public List read(String fileName, Class type) { + File file = getFileResource(fileName); + List resultList = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + + // 헤더 스킵 (첫 줄 읽기) + reader.readLine(); + + while ((line = reader.readLine()) != null) { + String[] values = line.split(","); + T obj = parseToObject(values, type); + resultList.add(obj); + } + } catch (IOException | ReflectiveOperationException e) { + throw new FileIOException(FILE_IO_ERROR); + } + + return resultList; + } + + @Override + public void write(T entity, String fileName, boolean append) { + File file = getFileResource(fileName); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, append))) { + if (file.length() == 0) { + writeHeaders(writer, entity); + } + writeEntity(writer, entity); + } catch (IOException e) { + throw new FileIOException(FILE_IO_ERROR); + } + } + + private T parseToObject(String[] values, Class type) + throws ReflectiveOperationException { + T instance = type.getDeclaredConstructor().newInstance(); + + Field[] fields = type.getDeclaredFields(); + for (int i = 0; i < values.length; i++) { + if (i < fields.length) { + Field field = fields[i]; + field.setAccessible(true); + field.set(instance, values[i]); + } + } + return instance; + } + + private File getFileResource(String fileName) { + try { + Resource resource = new PathResource(fileName); + if (!resource.exists()) { + resource.getFile().createNewFile(); + } + return resource.getFile(); + } catch (IOException e) { + throw new FileIOException(FILE_IO_ERROR); + } + } + + private void writeHeaders(BufferedWriter writer, T entity) throws IOException { + Field[] fields = entity.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + writer.write(fields[i].getName()); + if (i < fields.length - 1) { + writer.write(","); + } + } + writer.newLine(); + } + + private void writeEntity(BufferedWriter writer, T entity) throws IOException { + Field[] fields = entity.getClass().getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + fields[i].setAccessible(true); + try { + Object value = fields[i].get(entity); + writer.write(value != null ? value.toString() : ""); + } catch (IllegalAccessException e) { + throw new FileIOException(FILE_IO_ERROR); + } + if (i < fields.length - 1) { + writer.write(","); + } + } + writer.newLine(); + } + + +} diff --git a/src/main/java/com/programmers/springbootbasic/util/FileManager.java b/src/main/java/com/programmers/springbootbasic/util/FileManager.java new file mode 100644 index 0000000000..a2f5dabea8 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/util/FileManager.java @@ -0,0 +1,12 @@ +package com.programmers.springbootbasic.util; + +import java.util.List; + +public interface FileManager { + + boolean supports(String fileName); + + List read(String fileName, Class type); + + void write(T entity, String fileName, boolean append); +} diff --git a/src/main/java/com/programmers/springbootbasic/util/Messages.java b/src/main/java/com/programmers/springbootbasic/util/Messages.java new file mode 100644 index 0000000000..9383e604a1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/util/Messages.java @@ -0,0 +1,51 @@ +package com.programmers.springbootbasic.util; + +public enum Messages { + + SELECT_MENU(""" + === Voucher Program === + 종료 : exit + + === Voucher Menu === + 바우처 등록 : create voucher + 바우처 조회 : list voucher + 바우처 삭제 : delete voucher + 바우처 수정 : update voucher + + === User Menu === + 유저 등록 : register user + 유저 블랙리스트 조회 : list black user + 내 앞으로 바우처 등록 : register my voucher + 내 바우처 조회 : find my voucher + 내 바우처 삭제 : delete my voucher + 바우처를 소유한 유저 조회 : find users by voucher Id + """), + VOUCHER_REGISTER_TYPE("등록할 바우처의 타입을 입력하세요.(FIXED, PERCENT)"), + VOUCHER_REGISTER_AMOUNT("등록할 바우처의 금액을 입력하세요.(숫자)"), + INPUT_ID("ID를 입력하세요."), + INPUT_NICKNAME("닉네임을 입력하세요."), + VOUCHER_UPDATE_TYPE("수정할 바우처의 타입을 입력하세요.(FIXED, PERCENT)"), + VOUCHER_UPDATE_AMOUNT("수정할 바우처의 금액을 입력하세요.(숫자)"), + SUCCESS_VOUCHER_REGISTER("바우처가 등록되었습니다."), + SUCCESS_VOUCHER_LIST("바우처 목록입니다."), + SUCCESS_VOUCHER_FOUND("바우처 검색 결과 입니다."), + SUCCESS_VOUCHER_DELETE("바우처가 삭제되었습니다."), + SUCCESS_VOUCHER_UPDATE("바우처가 수정되었습니다."), + SUCCESS_BLACK_USER_LIST("블랙리스트 유저 목록입니다."), + SUCCESS_USER_VOUCHER_REGISTER("요청하신 바우처가 등록되었습니다."), + SUCCESS_USER_FOUND_BY_VOUCHER("요청하신 바우처를 소유한 유저 목록입니다."), + SUCCESS_USER_VOUCHER_FOUND_MINE("소유하신 바우처 목록입니다."), + SUCCESS_USER_VOUCHER_DELETE("요청하신 바우처가 삭제되었습니다."), + SUCCESS_USER_REGISTER("사용자가 등록되었습니다."), + ; + + Messages(String Message) { + this.message = Message; + } + + private final String message; + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/programmers/springbootbasic/util/ProdTimeGenerator.java b/src/main/java/com/programmers/springbootbasic/util/ProdTimeGenerator.java new file mode 100644 index 0000000000..02f917b2f2 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/util/ProdTimeGenerator.java @@ -0,0 +1,14 @@ +package com.programmers.springbootbasic.util; + +import com.programmers.springbootbasic.common.TimeGenerator; +import java.time.LocalDateTime; +import org.springframework.stereotype.Component; + +@Component +public class ProdTimeGenerator implements TimeGenerator { + + @Override + public LocalDateTime now() { + return LocalDateTime.now(); + } +} diff --git a/src/main/java/com/programmers/springbootbasic/util/SqlConverter.java b/src/main/java/com/programmers/springbootbasic/util/SqlConverter.java new file mode 100644 index 0000000000..d074dbd2c1 --- /dev/null +++ b/src/main/java/com/programmers/springbootbasic/util/SqlConverter.java @@ -0,0 +1,13 @@ +package com.programmers.springbootbasic.util; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class SqlConverter { + + public static LocalDateTime toLocalDateTime(String sqlDateTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + return LocalDateTime.parse(sqlDateTime, formatter); + } + +} diff --git a/src/main/resources/application-command.yml b/src/main/resources/application-command.yml new file mode 100644 index 0000000000..32ad8ae2b9 --- /dev/null +++ b/src/main/resources/application-command.yml @@ -0,0 +1,6 @@ +spring: + config: + activate: + on-profile: command + main: + web-application-type: none diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000000..97622732b2 --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,6 @@ +spring: + datasource: + url: jdbc:mysql://${TEST_HOST}/spring_basic + username: ${TEST_USERNAME} + password: ${TEST_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/application-tomcat.yml b/src/main/resources/application-tomcat.yml new file mode 100644 index 0000000000..5399c99624 --- /dev/null +++ b/src/main/resources/application-tomcat.yml @@ -0,0 +1,4 @@ +spring: + config: + activate: + on-profile: tomcat diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000000..9b2de0baec --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,24 @@ +server: + shutdown: graceful +spring: + profiles: + active: command + group: + tomcat: + - tomcat + command: + - command + test: + - test + lifecycle: + timeout-per-shutdown-phase: 10s + datasource: + url: jdbc:mysql://${HOST}/spring_basic + username: ${USERNAME} + password: ${PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver +file: + user: + path: blacklist.csv + voucher: + path: voucher.csv diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000000..8b4915e088 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + ERROR + ACCEPT + DENY + + + logs/error-%d{yyyy-MM-dd}.log + + + ${FILE_LOG_PATTERN} + + + + + + + + + diff --git a/src/main/resources/static/hug.png b/src/main/resources/static/hug.png new file mode 100644 index 0000000000..960b3cf5cc Binary files /dev/null and b/src/main/resources/static/hug.png differ diff --git a/src/main/resources/static/mycode.avif b/src/main/resources/static/mycode.avif new file mode 100644 index 0000000000..8ed2b1bf47 Binary files /dev/null and b/src/main/resources/static/mycode.avif differ diff --git a/src/main/resources/templates/customError.html b/src/main/resources/templates/customError.html new file mode 100644 index 0000000000..9c00893d58 --- /dev/null +++ b/src/main/resources/templates/customError.html @@ -0,0 +1,24 @@ + + + + + Error + + + + + +
+
+

+

+

Sorry, something is wrong.

+ +
+
+ + + diff --git a/src/main/resources/templates/defaultError.html b/src/main/resources/templates/defaultError.html new file mode 100644 index 0000000000..0c366b8d3d --- /dev/null +++ b/src/main/resources/templates/defaultError.html @@ -0,0 +1,24 @@ + + + + + Error + + + + + +
+
+

500

+

Error

+

Sorry, something is wrong.

+ +
+
+ + + diff --git a/src/main/resources/templates/home.html b/src/main/resources/templates/home.html new file mode 100644 index 0000000000..d70d5fd44c --- /dev/null +++ b/src/main/resources/templates/home.html @@ -0,0 +1,165 @@ + + + + + Home + + + + + + + +
+
+

This is Voucher management system

+

Thanks to visit here

+

Made By HappyJamy

+ +
+
+ + + diff --git a/src/main/resources/templates/user/blacklistedUsers.html b/src/main/resources/templates/user/blacklistedUsers.html new file mode 100644 index 0000000000..f8b47582f1 --- /dev/null +++ b/src/main/resources/templates/user/blacklistedUsers.html @@ -0,0 +1,143 @@ + + + + + Blacklisted Users + + + + + +
+

User Blacklist

+
+ + + + + + + + + + + + +
User nickname
+
+ +
+

No users are currently blacklisted.

+
+
+ + + + + diff --git a/src/main/resources/templates/user/register.html b/src/main/resources/templates/user/register.html new file mode 100644 index 0000000000..1ad9a359d6 --- /dev/null +++ b/src/main/resources/templates/user/register.html @@ -0,0 +1,145 @@ + + + + + Register + + + + + +

Sign up

+
+
+ +
+ + + + +
+ +
+ + + + + diff --git a/src/main/resources/templates/user/registrationComplete.html b/src/main/resources/templates/user/registrationComplete.html new file mode 100644 index 0000000000..1fab41c25f --- /dev/null +++ b/src/main/resources/templates/user/registrationComplete.html @@ -0,0 +1,142 @@ + + + + + Registration Complete + + + + + +
+
+
+

유저 등록 완료

+
+
+
+
+
User ID
+
UserID123
+
+
+
User Nickname
+
UserNickname
+
+
+
+
+ +

감사합니다! 사용자 등록이 완료되었습니다.

+
+ + + + + diff --git a/src/main/resources/templates/userVoucher/deleteComplete.html b/src/main/resources/templates/userVoucher/deleteComplete.html new file mode 100644 index 0000000000..a62085a1d3 --- /dev/null +++ b/src/main/resources/templates/userVoucher/deleteComplete.html @@ -0,0 +1,16 @@ + + + + + + Delete Complete + + + +
+

삭제 완료!

+

유저가 소유한 바우처 삭제가 완료되었습니다.

+ Back to Home +
+ + diff --git a/src/main/resources/templates/userVoucher/enterUserNickname.html b/src/main/resources/templates/userVoucher/enterUserNickname.html new file mode 100644 index 0000000000..583db4df13 --- /dev/null +++ b/src/main/resources/templates/userVoucher/enterUserNickname.html @@ -0,0 +1,140 @@ + + + + + User Nickname을 입력하세요 + + + + + +
+

Nickname 을 입력하세요

+

해당 닉네임을 가진 유저의 바우처를 조회 할 수 있습니다.

+
+ + +
+
+ +
+
+ + + + diff --git a/src/main/resources/templates/userVoucher/enterVoucherId.html b/src/main/resources/templates/userVoucher/enterVoucherId.html new file mode 100644 index 0000000000..e1b3b3dabf --- /dev/null +++ b/src/main/resources/templates/userVoucher/enterVoucherId.html @@ -0,0 +1,204 @@ + + + + + Voucher ID 를 선택하세요 + + + + + + +
+

Voucher ID 를 선택하세요

+

해당 Voucher ID를 가진 유저들을 조회 할 수 있습니다.

+
+ + + + + + + + + + + + + + + + +
Voucher IDTypeBenefit
+ +
+
+
+ +
+
+ + + diff --git a/src/main/resources/templates/userVoucher/register.html b/src/main/resources/templates/userVoucher/register.html new file mode 100644 index 0000000000..9a11f7c585 --- /dev/null +++ b/src/main/resources/templates/userVoucher/register.html @@ -0,0 +1,166 @@ + + + + Register Voucher + + + + + + +
+

Register Voucher

+

내 소유의 Voucher를 등록하세요.

+
+
+ + +
+
+ Available Vouchers: +
+ + +
+
+
+ +
+
+
+ + + + diff --git a/src/main/resources/templates/userVoucher/registrationComplete.html b/src/main/resources/templates/userVoucher/registrationComplete.html new file mode 100644 index 0000000000..c74be4dec7 --- /dev/null +++ b/src/main/resources/templates/userVoucher/registrationComplete.html @@ -0,0 +1,139 @@ + + + + + Registration Complete + + + + + +
+
+
+

바우처 등록 완료

+
+
+
+
+
User Voucher ID
+
123123
+
+
+
+
+ +

감사합니다! 바우처 등록이 완료되었습니다.

+
+ + + + + + diff --git a/src/main/resources/templates/userVoucher/userByVoucherIdResult.html b/src/main/resources/templates/userVoucher/userByVoucherIdResult.html new file mode 100644 index 0000000000..b6ff26ec32 --- /dev/null +++ b/src/main/resources/templates/userVoucher/userByVoucherIdResult.html @@ -0,0 +1,142 @@ + + + + + Find User By Voucher ID + + + + + +
+

User List

+

해당 바우처를 가지고 있는 유저 목록입니다.

+
+ + + + + + + + + + + + +
User nickname
+
+ +
+

No users.

+
+
+ + + diff --git a/src/main/resources/templates/userVoucher/voucherByUserNicknameResult.html b/src/main/resources/templates/userVoucher/voucherByUserNicknameResult.html new file mode 100644 index 0000000000..d8de8160fe --- /dev/null +++ b/src/main/resources/templates/userVoucher/voucherByUserNicknameResult.html @@ -0,0 +1,178 @@ + + + + + Vouchers of User + + + + + + + +
+

Vouchers of

+

해당 유저가 가지고 있는 바우처 목록입니다.

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ User Voucher Id + + Voucher UUID + + Voucher Type + + Benefit + + Details + + Del +
Voucher IDVoucher UUIDVoucher TypeBenefit + View + +
+ +
+
+
+ +
+

No Vouchers.

+
+
+ + + diff --git a/src/main/resources/templates/voucher/deleteComplete.html b/src/main/resources/templates/voucher/deleteComplete.html new file mode 100644 index 0000000000..7239a5bfec --- /dev/null +++ b/src/main/resources/templates/voucher/deleteComplete.html @@ -0,0 +1,16 @@ + + + + + + Delete Complete + + + +
+

삭제 완료!

+

바우처 삭제가 완료되었습니다.

+ Back to Home +
+ + diff --git a/src/main/resources/templates/voucher/details.html b/src/main/resources/templates/voucher/details.html new file mode 100644 index 0000000000..3838cd9ea4 --- /dev/null +++ b/src/main/resources/templates/voucher/details.html @@ -0,0 +1,134 @@ + + + + + + + Voucher Details + + + +
+
+
+

Voucher Details

+

Voucher Type:

+

Benefit:

+

Created At:

+ +
+ Edit +
+
+ + + + + + diff --git a/src/main/resources/templates/voucher/list.html b/src/main/resources/templates/voucher/list.html new file mode 100644 index 0000000000..c1a7e3c5c2 --- /dev/null +++ b/src/main/resources/templates/voucher/list.html @@ -0,0 +1,161 @@ + + + + + + Vouchers + + + + + +
+

Voucher List

+
+ + + + + + + + + + + + + + + + + + + + +
IDVoucherTypeBenefitActions
+ View + Edit +
+ +
+
+
+
+ + +
+

No Vouchers.

+
+
+ + + + diff --git a/src/main/resources/templates/voucher/register.html b/src/main/resources/templates/voucher/register.html new file mode 100644 index 0000000000..3d42e15fe5 --- /dev/null +++ b/src/main/resources/templates/voucher/register.html @@ -0,0 +1,167 @@ + + + + Create Voucher + + + + + + + + +
+
+
+
+

Create New Voucher

+

새로운 Voucher를 등록해보세요.

+
+
+ +
+ +
+ + + +
+
+
+
+ + +
+
+ +
+
+
+
+
+
+ + + diff --git a/src/main/resources/templates/voucher/registrationComplete.html b/src/main/resources/templates/voucher/registrationComplete.html new file mode 100644 index 0000000000..5d614646d7 --- /dev/null +++ b/src/main/resources/templates/voucher/registrationComplete.html @@ -0,0 +1,145 @@ + + + + + Registration Complete + + + + + +
+
+
+

바우처 등록 완료

+
+
+
+
+
Voucher Type
+
123123
+
+
+
+
+
Benefit
+
123123
+
+
+
+
+ +

감사합니다! 바우처 등록이 완료되었습니다.

+
+ + + + + + diff --git a/src/main/resources/templates/voucher/update.html b/src/main/resources/templates/voucher/update.html new file mode 100644 index 0000000000..1af9461d53 --- /dev/null +++ b/src/main/resources/templates/voucher/update.html @@ -0,0 +1,173 @@ + + + + Edit Voucher + + + + + + + + +
+
+
+
+

Edit Voucher

+

바우처 "" 를 수정합니다.

+
+
+ +
+ +
+ +
+
+
+
+ + +
+ +
+ +
+
+
+
+
+
+ + + + + + diff --git a/src/main/resources/templates/voucher/updateComplete.html b/src/main/resources/templates/voucher/updateComplete.html new file mode 100644 index 0000000000..35909d14dd --- /dev/null +++ b/src/main/resources/templates/voucher/updateComplete.html @@ -0,0 +1,147 @@ + + + + + Registration Complete + + + + + +
+
+
+

업데이트 완료

+
+
+
+
+
Voucher Type
+
123123
+
+
+
+
+
Benefit
+
123123
+
+
+
+
+ +

성공적으로 바우처 정보가 업데이트되었습니다.

+
+ + + + + + + + diff --git a/src/test/java/com/programmers/springbootbasic/domain/TestTimeGenerator.java b/src/test/java/com/programmers/springbootbasic/domain/TestTimeGenerator.java new file mode 100644 index 0000000000..f1663a6671 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/TestTimeGenerator.java @@ -0,0 +1,12 @@ +package com.programmers.springbootbasic.domain; + +import com.programmers.springbootbasic.common.TimeGenerator; +import java.time.LocalDateTime; + +public class TestTimeGenerator implements TimeGenerator { + + @Override + public LocalDateTime now() { + return LocalDateTime.of(2023, 10, 27, 15, 30, 0, 0); + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/TestVoucherIdGenerator.java b/src/test/java/com/programmers/springbootbasic/domain/TestVoucherIdGenerator.java new file mode 100644 index 0000000000..32463bfa98 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/TestVoucherIdGenerator.java @@ -0,0 +1,12 @@ +package com.programmers.springbootbasic.domain; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherIdGenerator; +import java.util.UUID; + +public class TestVoucherIdGenerator implements VoucherIdGenerator { + + @Override + public UUID generate() { + return UUID.fromString("00000000-0000-0000-0000-000000000000"); + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/user/application/UserServiceTest.java b/src/test/java/com/programmers/springbootbasic/domain/user/application/UserServiceTest.java new file mode 100644 index 0000000000..5e46812064 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/user/application/UserServiceTest.java @@ -0,0 +1,47 @@ +package com.programmers.springbootbasic.domain.user.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@DisplayName("UserService 테스트") +class UserServiceTest { + + @Mock + private UserRepository userRepository; + + @InjectMocks + private UserService userService; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + @DisplayName("차단 처리가 되어 있는 사용자 목록을 가져 온다.") + void success_findBlacklistedUsers() { + // given + User blockedUser = new User(1L, "user1", true); + when(userRepository.findBlacklistedUsers()).thenReturn(List.of(blockedUser)); + + // when + List result = userService.findBlacklistedUsers(); + + // then + assertThat(result).hasSize(1); + assertThat(result) + .extracting("nickname") + .containsExactlyInAnyOrder("user1"); + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/user/infrastructure/JdbcUserRepositoryTest.java b/src/test/java/com/programmers/springbootbasic/domain/user/infrastructure/JdbcUserRepositoryTest.java new file mode 100644 index 0000000000..7eec8d0bc4 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/user/infrastructure/JdbcUserRepositoryTest.java @@ -0,0 +1,266 @@ +package com.programmers.springbootbasic.domain.user.infrastructure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import java.util.List; +import java.util.Optional; +import java.util.Random; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +@DisplayName("JdbcUserRepository 테스트") +class JdbcUserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @DisplayName("Id 값이 null인 유저가 Id 값이 할당 되어 저장 된다.") + @Test + void success_save() { + //given + User user = new User(null, "user1", false); + + //when + User result = userRepository.save(user); + + //then + assertThat(userRepository.findAll()).hasSize(1); + assertNotNull(result.getId()); + assertThat(result).extracting("nickname", "blocked") + .containsExactly("user1", false); + } + + @DisplayName("Id 값이 null이 아닌 유저를 저장해도 Id 값이 DB에 의해 생성 된다.") + @Test + void success_save_userHasId() { + //given + var previousId = -1L; + User user = new User(previousId, "user1", false); + + //when + User result = userRepository.save(user); + + //then + assertThat(userRepository.findAll()).hasSize(1); + assertNotNull(result.getId()); + assertNotEquals(result.getId(), previousId); + assertThat(result).extracting("nickname", "blocked") + .containsExactly("user1", false); + } + + @DisplayName("존재하는 User Id 값으로 User를 찾는다.") + @Test + void success_findById() { + //given + User user1 = new User(null, "user1", false); + User user2 = new User(null, "user2", false); + User user3 = new User(null, "user3", true); + userRepository.save(user1); + var savedUser2 = userRepository.save(user2); + userRepository.save(user3); + + //when + Optional result = userRepository.findById(savedUser2.getId()); + + //then + assertThat(result).isPresent(); + assertThat(result.get()).extracting("nickname", "blocked") + .containsExactly("user2", false); + } + + @DisplayName("존재하지 않는 User Id 값으로 User를 찾으면 빈 값이 반환 된다.") + @Test + void success_findById_notFound() { + //given + User user1 = new User(null, "user1", false); + User user2 = new User(null, "user2", false); + User user3 = new User(null, "user3", true); + var savedUser1 = userRepository.save(user1); + var savedUser2 = userRepository.save(user2); + var savedUser3 = userRepository.save(user3); + List existedIds = List.of(savedUser1.getId(), savedUser2.getId(), savedUser3.getId()); + + Long notExistedId = createNotExistedId(existedIds); + + //when + Optional result = userRepository.findById(notExistedId); + + //then + assertThat(result).isEmpty(); + } + + @DisplayName("모든 User를 찾는다.") + @Test + void success_findAll() { + //given + User user1 = new User(null, "user1", false); + User user2 = new User(null, "user2", false); + User user3 = new User(null, "user3", true); + userRepository.save(user1); + userRepository.save(user2); + userRepository.save(user3); + + //when + var result = userRepository.findAll(); + + //then + assertThat(result).hasSize(3); + assertThat(result).extracting("nickname", "blocked") + .containsExactlyInAnyOrder( + tuple("user1", false), + tuple("user2", false), + tuple("user3", true) + ); + } + + @DisplayName("존재하는 User Id 값으로 User를 삭제할 수 있다.") + @Test + void success_deleteById() { + //given + User user1 = new User(null, "user1", false); + var savedUser = userRepository.save(user1); + + //when + var result = userRepository.deleteById(savedUser.getId()); + + //then + assertThat(userRepository.findAll()).hasSize(0); + assertThat(result).isEqualTo(1); + } + + @DisplayName("존재하지 않는 User Id 값으로 User를 삭제하면 0을 반환 한다.") + @Test + void success_deleteById_notFound() { + //given + User user1 = new User(null, "user1", false); + var savedUser = userRepository.save(user1); + List existedIds = List.of(savedUser.getId()); + + Long notExistedId = createNotExistedId(existedIds); + + //when + var result = userRepository.deleteById(notExistedId); + + //then + assertThat(userRepository.findAll()).hasSize(1); + assertThat(result).isEqualTo(0); + } + + @DisplayName("Id 값이 존재하는 User를 수정하면 1을 반환 한다.") + @Test + void success_update() { + //given + User user1 = new User(null, "user1", false); + var savedUser = userRepository.save(user1); + + //when + var result = userRepository.update(new User(savedUser.getId(), "user2", true)); + + //then + assertThat(userRepository.findAll()).hasSize(1); + assertThat(result).isEqualTo(1); + assertThat(userRepository.findById(savedUser.getId())).isPresent() + .get().extracting("nickname", "blocked") + .containsExactly("user2", true); + } + + @DisplayName("Id 값이 존재하지 않는 User를 수정하면 0을 반환 한다.") + @Test + void success_update_notFound() { + //given + User user1 = new User(null, "user1", false); + var savedUser = userRepository.save(user1); + List existedIds = List.of(savedUser.getId()); + + Long notExistedId = createNotExistedId(existedIds); + + //when + var result = userRepository.update(new User(notExistedId, "user2", true)); + + //then + assertThat(userRepository.findAll()).hasSize(1); + assertThat(result).isEqualTo(0); + } + + @DisplayName("블랙리스트에 등록 된 유저를 찾는다.") + @Test + void success_findBlacklistedUsers() { + //given + User user1 = new User(null, "user1", false); + User user2 = new User(null, "user2", false); + User user3 = new User(null, "user3", true); + userRepository.save(user1); + userRepository.save(user2); + userRepository.save(user3); + + //when + var result = userRepository.findBlacklistedUsers(); + + //then + assertThat(result).hasSize(1); + assertThat(result).extracting("nickname", "blocked") + .containsExactly(tuple("user3", true)); + } + + @DisplayName("존재하는 닉네임으로 유저를 찾는다.") + @Test + void success_findByNickname() { + //given + User user1 = new User(null, "user1", false); + User user2 = new User(null, "user2", false); + User user3 = new User(null, "user3", true); + userRepository.save(user1); + var savedUser2 = userRepository.save(user2); + userRepository.save(user3); + + //when + var result = userRepository.findByNickname("user2"); + + //then + assertThat(result) + .isPresent() + .get() + .extracting("id", "nickname", "blocked") + .containsExactly(savedUser2.getId(), "user2", false); + } + + @DisplayName("존재하지 않는 닉네임으로 유저를 찾으면 빈 값이 반환 된다.") + @Test + void success_findByNickname_notFound() { + //given + User user1 = new User(null, "user1", false); + User user2 = new User(null, "user2", false); + User user3 = new User(null, "user3", true); + userRepository.save(user1); + userRepository.save(user2); + userRepository.save(user3); + + //when + var result = userRepository.findByNickname("user4"); + + //then + assertThat(result).isEmpty(); + } + + private static Long createNotExistedId(List existedIds) { + Random random = new Random(); + Long notExistedId = null; + do { + var randomLong = random.nextLong(); + notExistedId = randomLong & Long.MAX_VALUE; + } while (existedIds.contains(notExistedId)); + return notExistedId; + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/user/presentation/UserControllerTest.java b/src/test/java/com/programmers/springbootbasic/domain/user/presentation/UserControllerTest.java new file mode 100644 index 0000000000..8510006157 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/user/presentation/UserControllerTest.java @@ -0,0 +1,46 @@ +package com.programmers.springbootbasic.domain.user.presentation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +import com.programmers.springbootbasic.domain.user.application.UserService; +import com.programmers.springbootbasic.domain.user.presentation.dto.UserResponse; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@DisplayName("UserController 테스트") +class UserControllerTest { + + @Mock + private UserService userService; + + @InjectMocks + private UserController userController; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @DisplayName("블랙리스트에 등록 된 유저가 반환 된다.") + @Test + void success_getBlackList() { + // given + List mockBlacklist = List.of( + new UserResponse("User1"), + new UserResponse("User2") + ); + when(userService.findBlacklistedUsers()).thenReturn(mockBlacklist); + + // when + List resultBlacklist = userController.getBlackList(); + + // then + assertEquals(mockBlacklist, resultBlacklist); + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/userVoucherWallet/application/UserVoucherWalletServiceTest.java b/src/test/java/com/programmers/springbootbasic/domain/userVoucherWallet/application/UserVoucherWalletServiceTest.java new file mode 100644 index 0000000000..3a3556d512 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/userVoucherWallet/application/UserVoucherWalletServiceTest.java @@ -0,0 +1,275 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.programmers.springbootbasic.common.TimeGenerator; +import com.programmers.springbootbasic.domain.TestTimeGenerator; +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.UserVoucherWalletRepository; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.entity.UserVoucherWallet; +import com.programmers.springbootbasic.domain.userVoucherWallet.presentation.dto.CreateUserVoucherWalletRequest; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.FixedAmountVoucher; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithUser; +import com.programmers.springbootbasic.domain.voucher.infrastructure.dto.UserVoucherWalletWithVoucher; +import com.programmers.springbootbasic.exception.exceptionClass.UserException; +import com.programmers.springbootbasic.exception.exceptionClass.UserVoucherWalletException; +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.util.ReflectionTestUtils; + +@ActiveProfiles("test") +@DisplayName("UserVoucherWalletService 테스트") +class UserVoucherWalletServiceTest { + + @InjectMocks + private UserVoucherWalletService userVoucherWalletService; + + @Mock + private UserVoucherWalletRepository userVoucherWalletRepository; + + @Mock + private UserRepository userRepository; + + @Mock + private VoucherRepository voucherRepository; + + private TimeGenerator timeGenerator; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + this.timeGenerator = new TestTimeGenerator(); + ReflectionTestUtils.setField(userVoucherWalletService, "timeGenerator", timeGenerator); + } + + @DisplayName("존재 하는 유저가 존재 하는 쿠폰을 등록 할 수 있다.") + @Test + void success_create() { + // given + var existedUUID = UUID.randomUUID(); + var existedNickname = "nickname"; + var userId = 1L; + var userVoucherWalletId = 1L; + CreateUserVoucherWalletRequest request = new CreateUserVoucherWalletRequest( + existedNickname, existedUUID + ); + when(userRepository.findByNickname(existedNickname)).thenReturn( + Optional.of(new User(userId, existedNickname, false))); + when(voucherRepository.findById(existedUUID)).thenReturn( + Optional.of( + new Voucher(existedUUID, new FixedAmountVoucher(100), 100, timeGenerator.now()) + ) + ); + when(userVoucherWalletRepository.save(any())) + .thenReturn( + new UserVoucherWallet(userVoucherWalletId, userId, existedUUID, timeGenerator.now()) + ); + + // when + var result = userVoucherWalletService.create(request); + + // then + assertThat(result).isEqualTo(userVoucherWalletId); + } + + @DisplayName("존재하지 않는 유저가 쿠폰을 등록 하려하면 예외가 발생한다.") + @Test + void fail_create_notExistedUser() { + // given + var existedUUID = UUID.randomUUID(); + var notExistedNickname = "notExistedNickname"; + CreateUserVoucherWalletRequest request = new CreateUserVoucherWalletRequest( + notExistedNickname, existedUUID + ); + when(userRepository.findByNickname(notExistedNickname)).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy(() -> userVoucherWalletService.create(request)) + .isInstanceOf(UserException.class) + .hasMessageContaining("사용자를 찾을 수 없습니다."); + } + + @DisplayName("존재하지 않는 쿠폰을 등록 하려 하면 예외가 발생한다.") + @Test + void success_create_notExistedVoucher() { + // given + var notExistedUUID = UUID.randomUUID(); + var existedNickname = "existedNickname"; + CreateUserVoucherWalletRequest request = new CreateUserVoucherWalletRequest( + existedNickname, notExistedUUID + ); + when(userRepository.findByNickname(existedNickname)).thenReturn( + Optional.of(new User(1L, existedNickname, false))); + when(voucherRepository.findById(notExistedUUID)).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy(() -> userVoucherWalletService.create(request)) + .isInstanceOf(VoucherException.class) + .hasMessageContaining("바우처를 찾을 수 없습니다."); + } + + @DisplayName("존재하는 바우처 아이디로 바우처를 소유한 유저를 조회 할 수 있다.") + @Test + void success_findUserByVoucherId() { + // given + var existedUUID = UUID.randomUUID(); + when(voucherRepository.findById(existedUUID)).thenReturn( + Optional.of(new Voucher(existedUUID, new FixedAmountVoucher(100), 100, timeGenerator.now())) + ); + when(userVoucherWalletRepository.findUserByVoucherId(existedUUID)).thenReturn( + List.of( + new UserVoucherWalletWithUser(1L, new User(1L, "nickname1", false)), + new UserVoucherWalletWithUser(2L, new User(2L, "nickname2", false)) + ) + ); + + // when + var result = userVoucherWalletService.findUserByVoucherId(existedUUID); + + // then + assertThat(result).hasSize(2); + } + + @DisplayName("바우처를 소유한 유저를 조회 할 때, 중복된 유저는 제거된다.") + @Test + void success_findUserByVoucherId_duplicatedUserVoucher() { + // given + var existedUUID = UUID.randomUUID(); + when(voucherRepository.findById(existedUUID)).thenReturn( + Optional.of(new Voucher(existedUUID, new FixedAmountVoucher(100), 100, timeGenerator.now())) + ); + when(userVoucherWalletRepository.findUserByVoucherId(existedUUID)).thenReturn( + List.of( + new UserVoucherWalletWithUser(1L, new User(1L, "nickname1", false)), + new UserVoucherWalletWithUser(2L, new User(1L, "nickname1", false)) + ) + ); + + // when + var result = userVoucherWalletService.findUserByVoucherId(existedUUID); + + // then + assertThat(result).hasSize(1); + } + + @DisplayName("바우처를 소유한 유저를 조회 할때 존재하지 않는 바우처 아이디 조회하면 예외가 발생한다.") + @Test + void fail_findUserByVoucherId_notExistedVoucher() { + // given + var notExistedUUID = UUID.randomUUID(); + when(voucherRepository.findById(notExistedUUID)).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy(() -> userVoucherWalletService.findUserByVoucherId(notExistedUUID)) + .isInstanceOf(VoucherException.class) + .hasMessageContaining("바우처를 찾을 수 없습니다."); + } + + @DisplayName("존재하는 유저의 닉네임으로 소유한 바우처를 조회 할 수 있다.") + @Test + void success_findVoucherByUserNickname() { + // given + var existedNickname = "nickname"; + var userId = 1L; + when(userRepository.findByNickname(existedNickname)).thenReturn( + Optional.of(new User(userId, existedNickname, false)) + ); + when(userVoucherWalletRepository.findVoucherByUserId(userId)).thenReturn( + List.of( + new UserVoucherWalletWithVoucher(1L, new Voucher(UUID.randomUUID(), + new FixedAmountVoucher(100), 100, timeGenerator.now())), + new UserVoucherWalletWithVoucher(2L, new Voucher(UUID.randomUUID(), + new FixedAmountVoucher(100), 100, timeGenerator.now())) + ) + ); + + // when + var result = userVoucherWalletService.findVoucherByUserNickname(existedNickname); + + // then + assertThat(result).hasSize(2); + } + + @DisplayName("존재하지 않는 유저의 닉네임으로 소유한 바우처를 조회 하려하면 예외가 발생한다.") + @Test + void fail_findVoucherByUserNickname_notExistedUser() { + // given + var notExistedNickname = "notExistedNickname"; + when(userRepository.findByNickname(notExistedNickname)).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy( + () -> userVoucherWalletService.findVoucherByUserNickname(notExistedNickname)) + .isInstanceOf(UserException.class) + .hasMessageContaining("사용자를 찾을 수 없습니다."); + } + + @DisplayName("id로 소유한 바우처를 삭제 할 수 있다.") + @Test + void success_deleteById() { + // given + var existedId = 1L; + when(userVoucherWalletRepository.findById(existedId)).thenReturn( + Optional.of( + new UserVoucherWallet(existedId, 1L, UUID.randomUUID(), timeGenerator.now())) + ); + when(userVoucherWalletRepository.deleteById(existedId)).thenReturn(1); + + // when + // then + assertThatCode(() -> userVoucherWalletService.deleteById(existedId)) + .doesNotThrowAnyException(); + } + + @DisplayName("존재하지 않는 id로 소유한 바우처를 삭제 하려하면 예외가 발생한다.") + @Test + void fail_deleteById_notExistedId() { + // given + var notExistedId = 1L; + when(userVoucherWalletRepository.findById(notExistedId)).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy(() -> userVoucherWalletService.deleteById(notExistedId)) + .isInstanceOf(UserVoucherWalletException.class) + .hasMessageContaining("사용자 바우처를 찾을 수 없습니다."); + } + + @DisplayName("존재하는 id로 소유한 바우처를 삭제 할 때, 삭제된 바우처가 없으면 예외가 발생한다.") + @Test + void success_deleteById_notDeletedUserVoucher() { + // given + var existedId = 1L; + when(userVoucherWalletRepository.findById(existedId)).thenReturn( + Optional.of( + new UserVoucherWallet(existedId, 1L, UUID.randomUUID(), timeGenerator.now())) + ); + when(userVoucherWalletRepository.deleteById(existedId)).thenReturn(0); + + // when + // then + assertThatThrownBy(() -> userVoucherWalletService.deleteById(existedId)) + .isInstanceOf(UserVoucherWalletException.class) + .hasMessageContaining("바우처 삭제에 실패하였습니다."); + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/userVoucherWallet/infrastructure/JdbcUserVoucherWalletRepositoryTest.java b/src/test/java/com/programmers/springbootbasic/domain/userVoucherWallet/infrastructure/JdbcUserVoucherWalletRepositoryTest.java new file mode 100644 index 0000000000..61de865eec --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/userVoucherWallet/infrastructure/JdbcUserVoucherWalletRepositoryTest.java @@ -0,0 +1,389 @@ +package com.programmers.springbootbasic.domain.userVoucherWallet.infrastructure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.programmers.springbootbasic.common.TimeGenerator; +import com.programmers.springbootbasic.domain.user.domain.UserRepository; +import com.programmers.springbootbasic.domain.user.domain.entity.User; +import com.programmers.springbootbasic.domain.TestTimeGenerator; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.UserVoucherWalletRepository; +import com.programmers.springbootbasic.domain.userVoucherWallet.domain.entity.UserVoucherWallet; +import com.programmers.springbootbasic.domain.voucher.domain.ProdVoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.FixedAmountVoucher; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +@DisplayName("JdbcUserVoucherWalletRepository 테스트") +class JdbcUserVoucherWalletRepositoryTest { + + @Autowired + private UserVoucherWalletRepository jdbcUserVoucherWalletRepository; + @Autowired + private UserRepository userRepository; + @Autowired + private VoucherRepository voucherRepository; + private final TimeGenerator timeGenerator = new TestTimeGenerator(); + private final VoucherIdGenerator idGenerator = new ProdVoucherIdGenerator(); + + @DisplayName("Id 값이 null인 UserVoucherWallet가 Id 값이 할당 되어 저장 된다.") + @Test + void success_save() { + // given + var userVoucherWallet = createUserVoucherWallet(); + + // when + var result = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + + // then + assertThat(jdbcUserVoucherWalletRepository.findAll()).hasSize(1); + assertNotNull(result.getId()); + assertThat(result).extracting("userId", "voucherId", "createdAt") + .containsExactly(userVoucherWallet.getUserId(), userVoucherWallet.getVoucherId(), + userVoucherWallet.getCreatedAt()); + } + + @DisplayName("Id 값이 null이 아닌 UserVoucherWallet를 저장해도 Id 값이 DB에 의해 생성 된다.") + @Test + void success_save_userVoucherWalletHasId() { + // given + var previousId = -1L; + var user = saveUser("user1"); + var voucher = saveVoucher(idGenerator.generate()); + var userVoucherWallet = new UserVoucherWallet(previousId, user.getId(), voucher.getId(), + timeGenerator.now()); + + // when + var result = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + + // then + assertThat(jdbcUserVoucherWalletRepository.findAll()).hasSize(1); + assertNotNull(result.getId()); + assertNotEquals(result.getId(), previousId); + assertThat(result).extracting("userId", "voucherId", "createdAt") + .containsExactly(userVoucherWallet.getUserId(), userVoucherWallet.getVoucherId(), + userVoucherWallet.getCreatedAt()); + } + + @DisplayName("존재하지 않는 User Id 값으로 UserVoucherWallet를 저장하면 예외가 발생한다.") + @Test + void fail_save_userIdNotExist() { + // given + var notExistUserId = 1L; + var voucher = saveVoucher(idGenerator.generate()); + var userVoucherWallet = new UserVoucherWallet(null, notExistUserId, voucher.getId(), + timeGenerator.now()); + + // when & then + assertThatThrownBy(() -> jdbcUserVoucherWalletRepository.save(userVoucherWallet)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + @DisplayName("존재하지 않는 Voucher Id 값으로 UserVoucherWallet를 저장하면 예외가 발생한다.") + @Test + void fail_save_voucherIdNotExist() { + // given + var user = saveUser("user1"); + var notExistVoucherId = idGenerator.generate(); + var userVoucherWallet = new UserVoucherWallet(null, user.getId(), notExistVoucherId, + timeGenerator.now()); + + // when & then + assertThatThrownBy(() -> jdbcUserVoucherWalletRepository.save(userVoucherWallet)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + @DisplayName("존재하는 UserVoucherWallet Id 값으로 UserVoucherWallet를 찾을 수 있다.") + @Test + void success_findById() { + // given + var userVoucherWallet = createUserVoucherWallet(); + var savedUserVoucherWallet = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + + // when + var result = jdbcUserVoucherWalletRepository.findById(savedUserVoucherWallet.getId()); + + // then + assertThat(result).isPresent(); + assertThat(result.get()).extracting("userId", "voucherId", "createdAt") + .containsExactly(userVoucherWallet.getUserId(), userVoucherWallet.getVoucherId(), + userVoucherWallet.getCreatedAt()); + } + + @DisplayName("모든 UserVoucherWallet를 찾을 수 있다.") + @Test + void success_findAll() { + // given + var userVoucherWallet1 = createUserVoucherWallet("user1", idGenerator.generate()); + var userVoucherWallet2 = createUserVoucherWallet("user2", idGenerator.generate()); + var userVoucherWallet3 = createUserVoucherWallet("user3", idGenerator.generate()); + jdbcUserVoucherWalletRepository.save(userVoucherWallet1); + jdbcUserVoucherWalletRepository.save(userVoucherWallet2); + jdbcUserVoucherWalletRepository.save(userVoucherWallet3); + + // when + var result = jdbcUserVoucherWalletRepository.findAll(); + + // then + assertThat(result).hasSize(3); + assertThat(result).extracting("userId", "voucherId", "createdAt") + .containsExactlyInAnyOrder( + tuple(userVoucherWallet1.getUserId(), userVoucherWallet1.getVoucherId(), + userVoucherWallet1.getCreatedAt()), + tuple(userVoucherWallet2.getUserId(), userVoucherWallet2.getVoucherId(), + userVoucherWallet2.getCreatedAt()), + tuple(userVoucherWallet3.getUserId(), userVoucherWallet3.getVoucherId(), + userVoucherWallet3.getCreatedAt()) + ); + } + + @DisplayName("존재하는 UserVoucherWallet Id 값으로 UserVoucherWallet를 삭제할 수 있다.") + @Test + void success_deleteById() { + // given + var userVoucherWallet = createUserVoucherWallet(); + var savedUserVoucherWallet = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + + // when + var result = jdbcUserVoucherWalletRepository.deleteById(savedUserVoucherWallet.getId()); + + // then + assertThat(result).isEqualTo(1); + assertThat(jdbcUserVoucherWalletRepository.findAll()).isEmpty(); + } + + @DisplayName("존재하지 않는 UserVoucherWallet Id 값으로 UserVoucherWallet를 삭제하면 0을 반환 한다.") + @Test + void fail_deleteById_notFound() { + // given + var userVoucherWallet = createUserVoucherWallet(); + var savedUserVoucherWallet = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + Long notExistId = createNotExistedId(List.of(savedUserVoucherWallet.getId())); + + // when + var result = jdbcUserVoucherWalletRepository.deleteById(notExistId); + + // then + assertThat(result).isEqualTo(0); + assertThat(jdbcUserVoucherWalletRepository.findAll()).hasSize(1); + } + + @DisplayName("존재하는 User와 Voucher id 값으로 UserVoucherWallet를 수정할 수 있다.") + @Test + void success_update() { + // given + var userVoucherWallet = createUserVoucherWallet(); + var savedUserVoucherWallet = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + + var newVoucherId = saveVoucher(idGenerator.generate()).getId(); + var newUserId = saveUser("user2").getId(); + var newUserVoucherWallet = new UserVoucherWallet(savedUserVoucherWallet.getId(), + newUserId, newVoucherId, savedUserVoucherWallet.getCreatedAt()); + + // when + var result = jdbcUserVoucherWalletRepository.update(newUserVoucherWallet); + + // then + assertThat(result).isEqualTo(1); + assertThat(jdbcUserVoucherWalletRepository.findAll()).hasSize(1); + assertThat(jdbcUserVoucherWalletRepository.findById(savedUserVoucherWallet.getId())) + .isPresent() + .get() + .extracting("userId", "voucherId", "createdAt") + .containsExactly(newUserVoucherWallet.getUserId(), newUserVoucherWallet.getVoucherId(), + newUserVoucherWallet.getCreatedAt()); + } + + @DisplayName("존재하지 않는 User와 Voucher id 값으로 UserVoucherWallet를 수정하면 예외를 발생한다.") + @Test + void fail_update_notFound() { + // given + var userVoucherWallet = createUserVoucherWallet(); + var savedUserVoucherWallet = jdbcUserVoucherWalletRepository.save(userVoucherWallet); + + var newVoucherId = idGenerator.generate(); + var newUserId = createNotExistedId(List.of(savedUserVoucherWallet.getUserId())); + var newUserVoucherWallet = new UserVoucherWallet(savedUserVoucherWallet.getId(), newUserId, + newVoucherId, + savedUserVoucherWallet.getCreatedAt()); + + // when & then + assertThatThrownBy(() -> jdbcUserVoucherWalletRepository.update(newUserVoucherWallet)) + .isInstanceOf(DataAccessException.class); + } + + @DisplayName("존재하는 User Id 값으로 해당 유저가 가진 Voucher들을 찾을 수 있다.") + @Test + void success_findVoucherByUserId() { + // given + var user1 = saveUser("user1"); + var user2 = saveUser("user2"); + var voucher1 = saveVoucher(idGenerator.generate()); + var voucher2 = saveVoucher(idGenerator.generate()); + var voucher3 = saveVoucher(idGenerator.generate()); + // user1 + var userVoucherWallet1 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var userVoucherWallet2 = new UserVoucherWallet(null, user1.getId(), voucher2.getId(), + timeGenerator.now()); + // user2 + var userVoucherWallet3 = new UserVoucherWallet(null, user2.getId(), voucher3.getId(), + timeGenerator.now()); + var savedUserVoucherWallet1 = jdbcUserVoucherWalletRepository.save(userVoucherWallet1); + var savedUserVoucherWallet2 = jdbcUserVoucherWalletRepository.save(userVoucherWallet2); + var savedUserVoucherWallet3 = jdbcUserVoucherWalletRepository.save(userVoucherWallet3); + + // when + var result = jdbcUserVoucherWalletRepository.findVoucherByUserId(user1.getId()); + + // then + assertThat(result).hasSize(2); + assertThat(result).extracting("id", "voucher") + .containsExactlyInAnyOrder( + tuple(savedUserVoucherWallet1.getId(), voucher1), + tuple(savedUserVoucherWallet2.getId(), voucher2) + ); + } + + @DisplayName("유저가 소유한 Voucher들을 조회 할때 같은 바우처를 여러개 가질 수 있다.") + @Test + void success_findVoucherByUserId_sameVoucher() { + // given + var user1 = saveUser("user1"); + var voucher1 = saveVoucher(idGenerator.generate()); + + var userVoucherWallet1 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var userVoucherWallet2 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var userVoucherWallet3 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var savedUserVoucherWallet1 = jdbcUserVoucherWalletRepository.save(userVoucherWallet1); + var savedUserVoucherWallet2 = jdbcUserVoucherWalletRepository.save(userVoucherWallet2); + var savedUserVoucherWallet3 = jdbcUserVoucherWalletRepository.save(userVoucherWallet3); + + // when + var result = jdbcUserVoucherWalletRepository.findVoucherByUserId(user1.getId()); + + // then + assertThat(result).hasSize(3); + assertThat(result).extracting("id", "voucher") + .containsExactlyInAnyOrder( + tuple(savedUserVoucherWallet1.getId(), voucher1), + tuple(savedUserVoucherWallet2.getId(), voucher1), + tuple(savedUserVoucherWallet3.getId(), voucher1) + ); + } + + @DisplayName("존재하는 Voucher Id 값으로 해당 Voucher를 가진 유저들을 찾을 수 있다.") + @Test + void success_findUserByVoucherId() { + // given + var user1 = saveUser("user1"); + var user2 = saveUser("user2"); + var voucher1 = saveVoucher(idGenerator.generate()); + var voucher2 = saveVoucher(idGenerator.generate()); + // user1 + var userVoucherWallet1 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var userVoucherWallet2 = new UserVoucherWallet(null, user1.getId(), voucher2.getId(), + timeGenerator.now()); + // user2 + var userVoucherWallet3 = new UserVoucherWallet(null, user2.getId(), voucher1.getId(), + timeGenerator.now()); + var savedUserVoucherWallet1 = jdbcUserVoucherWalletRepository.save(userVoucherWallet1); + jdbcUserVoucherWalletRepository.save(userVoucherWallet2); + var savedUserVoucherWallet3 = jdbcUserVoucherWalletRepository.save(userVoucherWallet3); + + // when + var result = jdbcUserVoucherWalletRepository.findUserByVoucherId(voucher1.getId()); + + // then + assertThat(result).hasSize(2); + assertThat(result).extracting("id", "user") + .containsExactlyInAnyOrder( + tuple(savedUserVoucherWallet1.getId(), user1), + tuple(savedUserVoucherWallet3.getId(), user2) + ); + } + + @DisplayName("특정 Voucher를 가진 유저들을 조회 할때 같은 유저가 여러개 있을 수 있다.") + @Test + void success_findUserByVoucherId_sameUser() { + // given + var user1 = saveUser("user1"); + var voucher1 = saveVoucher(idGenerator.generate()); + + var userVoucherWallet1 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var userVoucherWallet2 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var userVoucherWallet3 = new UserVoucherWallet(null, user1.getId(), voucher1.getId(), + timeGenerator.now()); + var savedUserVoucherWallet1 = jdbcUserVoucherWalletRepository.save(userVoucherWallet1); + var savedUserVoucherWallet2 = jdbcUserVoucherWalletRepository.save(userVoucherWallet2); + var savedUserVoucherWallet3 = jdbcUserVoucherWalletRepository.save(userVoucherWallet3); + + // when + var result = jdbcUserVoucherWalletRepository.findUserByVoucherId(voucher1.getId()); + + // then + assertThat(result).hasSize(3); + assertThat(result).extracting("id", "user") + .containsExactlyInAnyOrder( + tuple(savedUserVoucherWallet1.getId(), user1), + tuple(savedUserVoucherWallet2.getId(), user1), + tuple(savedUserVoucherWallet3.getId(), user1) + ); + } + + private User saveUser(String nickname) { + return userRepository.save(new User(null, nickname, false)); + } + + private Voucher saveVoucher(UUID id) { + return voucherRepository.save( + new Voucher(id, new FixedAmountVoucher(100), 100, timeGenerator.now()) + ); + } + + private UserVoucherWallet createUserVoucherWallet(String nickname, UUID id) { + var user = saveUser(nickname); + var voucher = saveVoucher(id); + var userVoucherWallet = new UserVoucherWallet(null, user.getId(), voucher.getId(), + timeGenerator.now()); + return userVoucherWallet; + } + + private UserVoucherWallet createUserVoucherWallet() { + return createUserVoucherWallet("user1", idGenerator.generate()); + } + + private static Long createNotExistedId(List existedIds) { + Random random = new Random(); + Long notExistedId = null; + do { + var randomLong = random.nextLong(); + notExistedId = randomLong & Long.MAX_VALUE; + } while (existedIds.contains(notExistedId)); + return notExistedId; + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/voucher/application/VoucherServiceTest.java b/src/test/java/com/programmers/springbootbasic/domain/voucher/application/VoucherServiceTest.java new file mode 100644 index 0000000000..8948b71a7b --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/voucher/application/VoucherServiceTest.java @@ -0,0 +1,174 @@ +package com.programmers.springbootbasic.domain.voucher.application; + +import static com.programmers.springbootbasic.exception.ErrorCode.NOT_FOUND_VOUCHER; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.programmers.springbootbasic.common.TimeGenerator; +import com.programmers.springbootbasic.domain.TestTimeGenerator; +import com.programmers.springbootbasic.domain.TestVoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.CreateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.UpdateVoucherRequest; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherResponse; +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.test.util.ReflectionTestUtils; + +class VoucherServiceTest { + + @InjectMocks + private VoucherService voucherService; + @Mock + private VoucherRepository voucherRepository; + private VoucherIdGenerator idGenerator; + private TimeGenerator timeGenerator; + + @BeforeEach + public void setUp() { + MockitoAnnotations.openMocks(this); + this.idGenerator = new TestVoucherIdGenerator(); + ReflectionTestUtils.setField(voucherService, "idGenerator", idGenerator); + this.timeGenerator = new TestTimeGenerator(); + ReflectionTestUtils.setField(voucherService, "timeGenerator", timeGenerator); + } + + @DisplayName("유효한 정보의 바우처를 생성 할 수 있다.") + @Test + void success_create() { + // given + CreateVoucherRequest request = CreateVoucherRequest.of(VoucherTypeEnum.FIXED, 10); + when(voucherRepository.save(any(Voucher.class))).thenReturn( + new Voucher(idGenerator.generate(), request.getVoucherType(), + request.getBenefitValue(), timeGenerator.now())); + + // when + var result = voucherService.create(request); + + // then + assertEquals(result, idGenerator.generate()); + } + + @DisplayName("모든 바우처를 조회 할 수 있다.") + @Test + void success_findAll() { + // given + when(voucherRepository.findAll()).thenReturn( + List.of( + new Voucher(idGenerator.generate(), VoucherTypeEnum.FIXED.getVoucherType(10), 10, + timeGenerator.now()), + new Voucher(idGenerator.generate(), VoucherTypeEnum.PERCENT.getVoucherType(10), 10, + timeGenerator.now()) + ) + ); + + // when + var result = voucherService.findAll(); + + // then + assertEquals(2, result.size()); + for (Object item : result) { + assertTrue(item instanceof VoucherResponse); + } + } + + @DisplayName("존재하는 바우처를 id로 조회 할 수 있다.") + @Test + void success_findById() { + // given + var voucher = new Voucher(idGenerator.generate(), VoucherTypeEnum.FIXED.getVoucherType(10), + 10, timeGenerator.now()); + when(voucherRepository.findById(idGenerator.generate())).thenReturn( + java.util.Optional.of(voucher)); + + // when + var result = voucherService.findById(voucher.getId()); + + // then + assertEquals(result.getId(), voucher.getId()); + } + + @DisplayName("존재하지 않는 바우처를 id로 조회 하려 하면 예외를 발생한다.") + @Test + void fail_findById() { + // given + when(voucherRepository.findById(any())).thenReturn(java.util.Optional.empty()); + + // when && then + assertThatThrownBy(() -> voucherService.findById(idGenerator.generate())) + .isInstanceOf(VoucherException.class) + .hasMessage(NOT_FOUND_VOUCHER.getMessage()); + } + + @DisplayName("존재하는 바우처를 id로 삭제 할 수 있다.") + @Test + void success_deleteById() { + // given + var voucher = new Voucher(idGenerator.generate(), VoucherTypeEnum.FIXED.getVoucherType(10), + 10, timeGenerator.now()); + when(voucherRepository.findById(voucher.getId())).thenReturn(Optional.of(voucher)); + when(voucherRepository.deleteById(voucher.getId())).thenReturn(1); + + // when && then + assertThatCode(() -> voucherService.deleteById(voucher.getId())) + .doesNotThrowAnyException(); + } + + @DisplayName("존재하지 않는 바우처를 id로 삭제 하려 하면 예외를 발생한다.") + @Test + void fail_deleteById() { + // given + when(voucherRepository.findById(any())).thenReturn(java.util.Optional.empty()); + + // when && then + assertThatThrownBy(() -> voucherService.deleteById(idGenerator.generate())) + .isInstanceOf(VoucherException.class) + .hasMessageContaining(NOT_FOUND_VOUCHER.getMessage()); + } + + @DisplayName("존재하는 바우처를 유효한 데이터로 수정 할 수 있다.") + @Test + void success_update() { + // given + var voucher = new Voucher(idGenerator.generate(), VoucherTypeEnum.FIXED.getVoucherType(10), + 10, timeGenerator.now()); + UpdateVoucherRequest request = new UpdateVoucherRequest(VoucherTypeEnum.PERCENT, 10); + when(voucherRepository.findById(voucher.getId())).thenReturn( + java.util.Optional.of(voucher)); + when(voucherRepository.update(any(Voucher.class))).thenReturn(1); + + // when + var result = voucherService.update(voucher.getId(), request); + + // then + assertEquals(result, voucher.getId()); + } + + @DisplayName("존재하지 않는 바우처를 유효한 데이터로 수정 하려 하면 예외를 발생한다.") + @Test + void fail_update_voucherNotFound() { + // given + UpdateVoucherRequest request = new UpdateVoucherRequest(VoucherTypeEnum.PERCENT, 10); + when(voucherRepository.findById(any())).thenReturn(java.util.Optional.empty()); + + // when && then + assertThatThrownBy(() -> voucherService.update(idGenerator.generate(), request)) + .isInstanceOf(VoucherException.class) + .hasMessageContaining(NOT_FOUND_VOUCHER.getMessage()); + } + +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/FixedAmountVoucherTest.java b/src/test/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/FixedAmountVoucherTest.java new file mode 100644 index 0000000000..01fbf6278e --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/FixedAmountVoucherTest.java @@ -0,0 +1,38 @@ +package com.programmers.springbootbasic.domain.voucher.domain.VoucherType; + +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_FIXED_VOUCHER_BENEFIT; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class FixedAmountVoucherTest { + + @DisplayName("유효한 데이터로 FixedAmountVoucher 인스턴스를 생성 할 수 있다.") + @Test + void success_createInstance() { + // given + Integer benefit = 10; + + // when + var result = new FixedAmountVoucher(benefit); + + // then + assertEquals(result.getVoucherTypeName(), "FIXED"); + } + + @DisplayName("benefit이 0 이하인 FixedAmountVoucher 인스턴스를 생성 할 수 없다.") + @Test + void fail_createInstance() { + // given + Integer benefit = -1; + + // when && then + assertThatThrownBy(() -> new FixedAmountVoucher(benefit)) + .isInstanceOf(VoucherException.class) + .hasMessageContaining(INVALID_FIXED_VOUCHER_BENEFIT.getMessage()); + } + +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/PercentDiscountVoucherTest.java b/src/test/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/PercentDiscountVoucherTest.java new file mode 100644 index 0000000000..19b9e5d268 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/voucher/domain/VoucherType/PercentDiscountVoucherTest.java @@ -0,0 +1,43 @@ +package com.programmers.springbootbasic.domain.voucher.domain.VoucherType; + +import static com.programmers.springbootbasic.exception.ErrorCode.INVALID_PERCENT_VOUCHER_BENEFIT; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.programmers.springbootbasic.exception.exceptionClass.VoucherException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PercentDiscountVoucherTest { + + @DisplayName("유효한 데이터로 PercentDiscountVoucher 인스턴스를 생성 할 수 있다.") + @Test + void success_createInstance() { + // given + Integer benefit = 10; + + // when + var result = new PercentDiscountVoucher(benefit); + + // then + assertEquals(result.getVoucherTypeName(), "PERCENT"); + } + + @DisplayName("benefit이 0 이하 100 초과인 PercentDiscountVoucher 인스턴스를 생성 할 수 없다.") + @Test + void fail_createInstance() { + // given + Integer benefit1 = -1; + Integer benefit2 = 101; + + // when && then + assertThatThrownBy(() -> new PercentDiscountVoucher(benefit1)) + .isInstanceOf(VoucherException.class) + .hasMessageContaining(INVALID_PERCENT_VOUCHER_BENEFIT.getMessage()); + + assertThatThrownBy(() -> new PercentDiscountVoucher(benefit2)) + .isInstanceOf(VoucherException.class) + .hasMessageContaining(INVALID_PERCENT_VOUCHER_BENEFIT.getMessage()); + } + +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/voucher/infrastructure/JdbcVoucherRepositoryTest.java b/src/test/java/com/programmers/springbootbasic/domain/voucher/infrastructure/JdbcVoucherRepositoryTest.java new file mode 100644 index 0000000000..f632e572dd --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/voucher/infrastructure/JdbcVoucherRepositoryTest.java @@ -0,0 +1,194 @@ +package com.programmers.springbootbasic.domain.voucher.infrastructure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; + +import com.programmers.springbootbasic.common.TimeGenerator; +import com.programmers.springbootbasic.domain.TestTimeGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.ProdVoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherIdGenerator; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherRepository; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.FixedAmountVoucher; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.PercentDiscountVoucher; +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import com.programmers.springbootbasic.domain.voucher.domain.entity.Voucher; +import com.programmers.springbootbasic.domain.voucher.presentation.dto.VoucherCriteria; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DataAccessException; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +@SpringBootTest +@Transactional +@ActiveProfiles("test") +@DisplayName("JdbcVoucherRepositoryTest 테스트") +class JdbcVoucherRepositoryTest { + + @Autowired + private VoucherRepository voucherRepository; + private final VoucherIdGenerator idGenerator = new ProdVoucherIdGenerator(); + private final TimeGenerator timeGenerator = new TestTimeGenerator(); + + @DisplayName("Voucher를 저장할 수 있다.") + @Test + void success_save() { + // given + Voucher voucher = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + timeGenerator.now()); + + // when + Voucher savedVoucher = voucherRepository.save(voucher); + + // then + assertThat(voucherRepository.findAll()).hasSize(1); + assertThat(savedVoucher).isEqualTo(voucher); + } + + @DisplayName("존재하는 Voucher Id 값으로 Voucher를 조회할 수 있다.") + @Test + void success_findById() { + // given + Voucher voucher = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + timeGenerator.now()); + voucherRepository.save(voucher); + + // when + var result = voucherRepository.findById(voucher.getId()); + + // then + assertThat(result).isPresent().get().isEqualTo(voucher); + } + + @DisplayName("모든 Voucher를 조회할 수 있다.") + @Test + void success_findAll() { + // given + Voucher voucher1 = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + timeGenerator.now()); + Voucher voucher2 = new Voucher(idGenerator.generate(), new FixedAmountVoucher(200), 200, + timeGenerator.now()); + voucherRepository.save(voucher1); + voucherRepository.save(voucher2); + + // when + var result = voucherRepository.findAll(); + + // then + assertThat(result).hasSize(2); + } + + @DisplayName("존재하는 Voucher Id 값으로 Voucher를 삭제할 수 있다.") + @Test + void success_deleteById() { + // given + Voucher voucher = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + timeGenerator.now()); + voucherRepository.save(voucher); + + // when + int result = voucherRepository.deleteById(voucher.getId()); + + // then + assertThat(result).isEqualTo(1); + assertThat(voucherRepository.findAll()).isEmpty(); + } + + @DisplayName("존재하지 않는 ID로 Voucher를 삭제할 때, 0을 반환한다.") + @Test + void fail_deleteById_notFound() { + // given + UUID nonExistentId = idGenerator.generate(); + + // when + int result = voucherRepository.deleteById(nonExistentId); + + // then + assertThat(result).isZero(); + } + + @DisplayName("Voucher 정보를 업데이트 할 수 있다.") + @Test + void success_update() { + // given + Voucher voucher = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + timeGenerator.now()); + voucherRepository.save(voucher); + Voucher updatedVoucher = new Voucher(voucher.getId(), new FixedAmountVoucher(200), 200, + timeGenerator.now()); + + // when + int result = voucherRepository.update(updatedVoucher); + + // then + assertThat(result).isEqualTo(1); + assertThat(voucherRepository.findById(voucher.getId()).get().getBenefitValue()) + .isEqualTo(200); + } + + @DisplayName("누락된 필드 정보로 Voucher를 업데이트 할 때, 예외가 발생한다.") + @Test + void fail_update_missingFields() { + // given + Voucher voucher = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + timeGenerator.now()); + voucherRepository.save(voucher); + Voucher incompleteVoucher = new Voucher(voucher.getId(), null, 200, + timeGenerator.now()); // Type 정보 누락 + Voucher incompleteVoucher2 = new Voucher(voucher.getId(), new FixedAmountVoucher(200), + null, timeGenerator.now()); // BenefitValue 정보 누락 + + // when && then + assertThatThrownBy(() -> voucherRepository.update(incompleteVoucher)) + .isInstanceOf(NullPointerException.class); + assertThatThrownBy(() -> voucherRepository.update(incompleteVoucher2)) + .isInstanceOf(DataAccessException.class); + } + + + @Test + void findByCriteria() { + // given + Voucher voucher1 = new Voucher(idGenerator.generate(), new FixedAmountVoucher(100), 100, + LocalDateTime.of(2023, 10, 1, 0, 0, 0)); + Voucher voucher2 = new Voucher(idGenerator.generate(), new PercentDiscountVoucher(50), 50, + LocalDateTime.of(2023, 10, 3, 0, 0, 0)); + voucherRepository.save(voucher1); + voucherRepository.save(voucher2); + + // when + var dateResult = voucherRepository.findByCriteria(new VoucherCriteria( + LocalDate.of(2023, 10, 1), + LocalDate.of(2023, 10, 1), + null + )); + + var typeResult = voucherRepository.findByCriteria(new VoucherCriteria( + null, + null, + VoucherTypeEnum.FIXED + )); + + var dateAndTypeResult = voucherRepository.findByCriteria(new VoucherCriteria( + LocalDate.of(2023, 10, 1), + LocalDate.of(2023, 10, 1), + VoucherTypeEnum.FIXED + )); + + // then + assertThat(dateResult).hasSize(1).extracting("createdAt") + .containsExactly(LocalDateTime.of(2023, 10, 1, 0, 0, 0)); + assertThat(typeResult).hasSize(1).extracting("voucherType") + .containsExactly(new FixedAmountVoucher(100)); + assertThat(dateAndTypeResult).hasSize(1).extracting("createdAt", "voucherType") + .containsExactly( + tuple(LocalDateTime.of(2023, 10, 1, 0, 0, 0), + new FixedAmountVoucher(100))); + } +} diff --git a/src/test/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/CreateVoucherRequestTest.java b/src/test/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/CreateVoucherRequestTest.java new file mode 100644 index 0000000000..e68ca48463 --- /dev/null +++ b/src/test/java/com/programmers/springbootbasic/domain/voucher/presentation/dto/CreateVoucherRequestTest.java @@ -0,0 +1,25 @@ +package com.programmers.springbootbasic.domain.voucher.presentation.dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.programmers.springbootbasic.domain.voucher.domain.VoucherType.VoucherTypeEnum; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CreateVoucherRequestTest { + + @DisplayName("of 메서드를 통해 적절한 CreateVoucherRequest 객체를 반환한다.") + @Test + void success_of() { + // given + VoucherTypeEnum voucherType = VoucherTypeEnum.FIXED; + Integer benefitValue = 100; + + // when + CreateVoucherRequest request = CreateVoucherRequest.of(voucherType, benefitValue); + + // then + assertThat(request.getVoucherType()).isNotNull(); + assertThat(request.getBenefitValue()).isEqualTo(benefitValue); + } +} diff --git a/voucher.csv b/voucher.csv new file mode 100644 index 0000000000..26fd26a0bb --- /dev/null +++ b/voucher.csv @@ -0,0 +1,2 @@ +"BENEFITVALUE","ID","VOUCHERTYPE" +"12","d1922fff-145d-4ea5-8ad9-a823571bca19","FIXED"