diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..2412e7a3dd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM mysql:latest + +ENV MYSQL_ROOT_PASSWORD=1234 +ENV MYSQL_DATABASE=voucher_management + +COPY ./init.sql /docker-entrypoint-initdb.d/ diff --git a/build.gradle b/build.gradle index ae92e0119f..a6a7675bc0 100644 --- a/build.gradle +++ b/build.gradle @@ -25,6 +25,10 @@ dependencies { testImplementation 'org.testcontainers:testcontainers:1.19.1' testImplementation 'org.testcontainers:mysql:1.19.1' testImplementation 'org.testcontainers:junit-jupiter:1.19.1' + implementation 'org.springframework.boot:spring-boot-starter-web:3.1.4' + implementation 'org.springframework.boot:spring-boot-starter-validation:3.1.4' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.15.2' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.4' } test { diff --git a/init.sql b/init.sql new file mode 100644 index 0000000000..3d08a794ca --- /dev/null +++ b/init.sql @@ -0,0 +1,14 @@ +CREATE TABLE customers ( + customer_id BINARY(16) PRIMARY KEY, + name VARCHAR(30) NOT NULL, + customer_type VARCHAR(10) NOT NULL default 'NORMAL' +); + +CREATE TABLE vouchers ( + voucher_id BINARY(16) PRIMARY KEY, + created_at DATETIME NOT NULL, + discount_value DECIMAL NOT NULL, + voucher_type VARCHAR(10) NOT NULL, + customer_id BINARY(16), + FOREIGN KEY (customer_id) REFERENCES customers(customer_id) +) diff --git a/src/main/java/com/programmers/vouchermanagement/VoucherManagementApplication.java b/src/main/java/com/programmers/vouchermanagement/VoucherManagementApplication.java index e4b73b8d04..0545ccd8e6 100644 --- a/src/main/java/com/programmers/vouchermanagement/VoucherManagementApplication.java +++ b/src/main/java/com/programmers/vouchermanagement/VoucherManagementApplication.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement; import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationPropertiesScan; @@ -12,7 +13,19 @@ public class VoucherManagementApplication { public static void main(String[] args) { SpringApplication application = new SpringApplication(VoucherManagementApplication.class); application.setAdditionalProfiles("file"); + setApplicationMode(application); application.run(args); } + private static void setApplicationMode(SpringApplication application) { + if (isWebMode(application)) { + return; + } + application.setWebApplicationType(WebApplicationType.NONE); + } + + private static boolean isWebMode(SpringApplication application) { + return application.getAdditionalProfiles().contains("web"); + } + } diff --git a/src/main/java/com/programmers/vouchermanagement/advice/ExceptionMessage.java b/src/main/java/com/programmers/vouchermanagement/advice/ExceptionMessage.java new file mode 100644 index 0000000000..4bd9dba6fc --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/advice/ExceptionMessage.java @@ -0,0 +1,13 @@ +package com.programmers.vouchermanagement.advice; + +public class ExceptionMessage { + private final String message; + + public ExceptionMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/advice/GeneralAdvice.java b/src/main/java/com/programmers/vouchermanagement/advice/GeneralAdvice.java new file mode 100644 index 0000000000..df23d95f08 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/advice/GeneralAdvice.java @@ -0,0 +1,31 @@ +package com.programmers.vouchermanagement.advice; + +import java.util.NoSuchElementException; + +import org.springframework.ui.Model; +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; + +import com.programmers.vouchermanagement.advice.annotation.AdminController; + +@ControllerAdvice(annotations = AdminController.class) +public class GeneralAdvice { + + @ExceptionHandler({IllegalArgumentException.class, NoSuchElementException.class, RuntimeException.class}) + public String renderErrorPage(RuntimeException exception, Model model) { + model.addAttribute("message", exception.getMessage()); + return "error"; + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public String handleInvalidMethodArgumentException(BindingResult bindingResult, Model model) { + FieldError error = bindingResult.getFieldErrors() + .get(0); + String message = error.getField() + " exception - " + error.getDefaultMessage(); + model.addAttribute("message", message); + return "error"; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/advice/RestAdvice.java b/src/main/java/com/programmers/vouchermanagement/advice/RestAdvice.java new file mode 100644 index 0000000000..7369088130 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/advice/RestAdvice.java @@ -0,0 +1,43 @@ +package com.programmers.vouchermanagement.advice; + +import java.util.NoSuchElementException; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice(annotations = RestController.class) +public class RestAdvice { + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ExceptionMessage handleInvalidMethodArgumentException(BindingResult bindingResult) { + FieldError error = bindingResult.getFieldErrors() + .get(0); + String message = error.getField() + " exception - " + error.getDefaultMessage(); + return new ExceptionMessage(message); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ExceptionMessage handleIllegalArgumentException(IllegalArgumentException exception) { + return new ExceptionMessage(exception.getMessage()); + } + + @ExceptionHandler(NoSuchElementException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ExceptionMessage handleNoSuchElementException(NoSuchElementException exception) { + return new ExceptionMessage(exception.getMessage()); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ExceptionMessage handleRuntimeException(RuntimeException exception) { + return new ExceptionMessage("Unexpected message appears. Please contact to DEV team"); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/advice/annotation/AdminController.java b/src/main/java/com/programmers/vouchermanagement/advice/annotation/AdminController.java new file mode 100644 index 0000000000..46b822b570 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/advice/annotation/AdminController.java @@ -0,0 +1,16 @@ +package com.programmers.vouchermanagement.advice.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Controller; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Controller +public @interface AdminController { +} diff --git a/src/main/java/com/programmers/vouchermanagement/configuration/DBConfig.java b/src/main/java/com/programmers/vouchermanagement/configuration/DBConfig.java index b96c88ee50..8a854f9760 100644 --- a/src/main/java/com/programmers/vouchermanagement/configuration/DBConfig.java +++ b/src/main/java/com/programmers/vouchermanagement/configuration/DBConfig.java @@ -4,15 +4,16 @@ import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import com.programmers.vouchermanagement.configuration.profiles.DBEnabledCondition; import com.programmers.vouchermanagement.configuration.properties.datasource.DataSourceProperties; import com.zaxxer.hikari.HikariDataSource; @Configuration -@Profile("jdbc") +@Conditional(DBEnabledCondition.class) public class DBConfig { @Bean public DataSource dataSource(DataSourceProperties dataSourceProperties) { diff --git a/src/main/java/com/programmers/vouchermanagement/configuration/FileConfig.java b/src/main/java/com/programmers/vouchermanagement/configuration/FileConfig.java index 78000a4f8f..377d9be576 100644 --- a/src/main/java/com/programmers/vouchermanagement/configuration/FileConfig.java +++ b/src/main/java/com/programmers/vouchermanagement/configuration/FileConfig.java @@ -3,16 +3,17 @@ import java.util.UUID; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; import com.fasterxml.jackson.databind.ObjectMapper; +import com.programmers.vouchermanagement.configuration.profiles.FileEnabledCondition; import com.programmers.vouchermanagement.customer.domain.Customer; import com.programmers.vouchermanagement.util.JSONFileManager; import com.programmers.vouchermanagement.voucher.domain.Voucher; @Configuration -@Profile({"file", "test"}) +@Conditional(FileEnabledCondition.class) public class FileConfig { @Bean public ObjectMapper objectMapper() { diff --git a/src/main/java/com/programmers/vouchermanagement/configuration/profiles/ConsoleCondition.java b/src/main/java/com/programmers/vouchermanagement/configuration/profiles/ConsoleCondition.java new file mode 100644 index 0000000000..28592dfb9f --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/configuration/profiles/ConsoleCondition.java @@ -0,0 +1,12 @@ +package com.programmers.vouchermanagement.configuration.profiles; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class ConsoleCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return context.getEnvironment().matchesProfiles("dev", "file", "jdbc"); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/configuration/profiles/DBEnabledCondition.java b/src/main/java/com/programmers/vouchermanagement/configuration/profiles/DBEnabledCondition.java new file mode 100644 index 0000000000..6dd2b905a8 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/configuration/profiles/DBEnabledCondition.java @@ -0,0 +1,12 @@ +package com.programmers.vouchermanagement.configuration.profiles; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class DBEnabledCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return context.getEnvironment().matchesProfiles("jdbc", "web"); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/configuration/profiles/FileEnabledCondition.java b/src/main/java/com/programmers/vouchermanagement/configuration/profiles/FileEnabledCondition.java new file mode 100644 index 0000000000..397945836b --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/configuration/profiles/FileEnabledCondition.java @@ -0,0 +1,12 @@ +package com.programmers.vouchermanagement.configuration.profiles; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class FileEnabledCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + return context.getEnvironment().matchesProfiles("file", "test"); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/configuration/properties/datasource/DataSourceProperties.java b/src/main/java/com/programmers/vouchermanagement/configuration/properties/datasource/DataSourceProperties.java index 4dcb918c77..25915c2013 100644 --- a/src/main/java/com/programmers/vouchermanagement/configuration/properties/datasource/DataSourceProperties.java +++ b/src/main/java/com/programmers/vouchermanagement/configuration/properties/datasource/DataSourceProperties.java @@ -2,9 +2,11 @@ import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.ConstructorBinding; -import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Conditional; -@Profile("jdbc") +import com.programmers.vouchermanagement.configuration.profiles.DBEnabledCondition; + +@Conditional(DBEnabledCondition.class) @ConfigurationProperties("datasource") public class DataSourceProperties { private final String driverClassName; diff --git a/src/main/java/com/programmers/vouchermanagement/consoleapp/io/ConsoleManager.java b/src/main/java/com/programmers/vouchermanagement/consoleapp/io/ConsoleManager.java index 07f44f35c9..bf2f74dbdd 100644 --- a/src/main/java/com/programmers/vouchermanagement/consoleapp/io/ConsoleManager.java +++ b/src/main/java/com/programmers/vouchermanagement/consoleapp/io/ConsoleManager.java @@ -1,6 +1,5 @@ package com.programmers.vouchermanagement.consoleapp.io; -import java.math.BigDecimal; import java.util.List; import java.util.UUID; @@ -14,7 +13,6 @@ import com.programmers.vouchermanagement.customer.dto.CustomerResponse; import com.programmers.vouchermanagement.customer.dto.UpdateCustomerRequest; import com.programmers.vouchermanagement.util.Formatter; -import com.programmers.vouchermanagement.voucher.domain.VoucherType; import com.programmers.vouchermanagement.voucher.dto.CreateVoucherRequest; import com.programmers.vouchermanagement.voucher.dto.UpdateVoucherRequest; import com.programmers.vouchermanagement.voucher.dto.VoucherCustomerRequest; @@ -77,6 +75,7 @@ public class ConsoleManager { private static final String LISTING_OWNED_VOUCHERS_OF = "Listing vouchers that Customer %s has ..."; private static final String GRANT_SUCCESSFUL = "Voucher (%s) is granted to Customer (%s)."; private static final String VOUCHER_OWNER_BELOW = "The owner of Voucher %s is provided below:"; + private static final String UNKNOWN_EXCEPTION_THROWN = "Failed to run the command. Unknown Exception is thrown."; private final TextIO textIO; @@ -100,11 +99,9 @@ public CustomerMenu selectCustomerMenu() { } public CreateVoucherRequest instructCreateVoucher() { - String voucherTypeCode = read(VOUCHER_TYPE_INPUT); - VoucherType voucherType = VoucherType.findVoucherTypeByCode(voucherTypeCode); - + String voucherType = read(VOUCHER_TYPE_INPUT); String discountValueInput = read(VOUCHER_DISCOUNT_VALUE_INSTRUCTION); - BigDecimal discountValue = new BigDecimal(discountValueInput); + long discountValue = Long.parseLong(discountValueInput); return new CreateVoucherRequest(discountValue, voucherType); } @@ -112,12 +109,10 @@ public UpdateVoucherRequest instructUpdateVoucher() { String voucherIdInput = read(ID_INPUT.formatted(CONTENT_VOUCHER)); UUID voucherId = UUID.fromString(voucherIdInput); - String discountValueInput = read(VOUCHER_DISCOUNT_VALUE_INSTRUCTION); - BigDecimal discountValue = new BigDecimal(discountValueInput); + String discountValue = read(VOUCHER_DISCOUNT_VALUE_INSTRUCTION); String voucherTypeCode = read(VOUCHER_TYPE_INPUT); - VoucherType voucherType = VoucherType.findVoucherTypeByCode(voucherTypeCode); - return new UpdateVoucherRequest(voucherId, discountValue, voucherType); + return new UpdateVoucherRequest(voucherId, Long.parseLong(discountValue), voucherTypeCode); } public String instructCreateCustomer() { @@ -152,7 +147,7 @@ public VoucherCustomerRequest instructRequestVoucherCustomer() { } public void printSaveVoucherResult(VoucherResponse voucherResponse) { - print(CREATE_SUCCESS_MESSAGE.formatted(CONTENT_VOUCHER, voucherResponse.getVoucherId())); + print(CREATE_SUCCESS_MESSAGE.formatted(CONTENT_VOUCHER, voucherResponse.voucherId())); } public void printSaveCustomerResult(CustomerResponse customerResponse) { @@ -220,6 +215,9 @@ public void printIncorrectMenu() { } public void printException(RuntimeException e) { + if (e.getMessage() == null) { + print(UNKNOWN_EXCEPTION_THROWN); + } print(e.getMessage()); } diff --git a/src/main/java/com/programmers/vouchermanagement/consoleapp/runner/ConsoleApp.java b/src/main/java/com/programmers/vouchermanagement/consoleapp/runner/ConsoleApp.java index 26fbd721d5..b224e3d3bd 100644 --- a/src/main/java/com/programmers/vouchermanagement/consoleapp/runner/ConsoleApp.java +++ b/src/main/java/com/programmers/vouchermanagement/consoleapp/runner/ConsoleApp.java @@ -4,15 +4,16 @@ import org.slf4j.LoggerFactory; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; -import org.springframework.context.annotation.Profile; +import org.springframework.context.annotation.Conditional; import org.springframework.stereotype.Component; +import com.programmers.vouchermanagement.configuration.profiles.ConsoleCondition; import com.programmers.vouchermanagement.consoleapp.io.ConsoleManager; import com.programmers.vouchermanagement.consoleapp.menu.Menu; import com.programmers.vouchermanagement.consoleapp.menu.MenuHandler; @Component -@Profile({"jdbc, dev, file"}) +@Conditional(ConsoleCondition.class) public class ConsoleApp implements ApplicationRunner { private static final Logger logger = LoggerFactory.getLogger(ConsoleApp.class); private static final String INCORRECT_MESSAGE = "Selected menu is not an executable menu."; diff --git a/src/main/java/com/programmers/vouchermanagement/customer/controller/CustomerAdminController.java b/src/main/java/com/programmers/vouchermanagement/customer/controller/CustomerAdminController.java new file mode 100644 index 0000000000..d1845e8752 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/customer/controller/CustomerAdminController.java @@ -0,0 +1,71 @@ +package com.programmers.vouchermanagement.customer.controller; + +import java.util.List; +import java.util.UUID; + +import jakarta.validation.Valid; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +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.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.programmers.vouchermanagement.advice.annotation.AdminController; +import com.programmers.vouchermanagement.customer.domain.Customer; +import com.programmers.vouchermanagement.customer.dto.CustomerResponse; +import com.programmers.vouchermanagement.customer.dto.UpdateCustomerRequest; +import com.programmers.vouchermanagement.customer.service.CustomerService; + +@AdminController +@ConditionalOnWebApplication +@RequestMapping("/customers") +public class CustomerAdminController { + private final CustomerService customerService; + + public CustomerAdminController(CustomerService customerService) { + this.customerService = customerService; + } + + @GetMapping + public String readAllCustomers(Model model) { + List customers = customerService.findAll(); + model.addAttribute("customers", customers); + return "customers/customers"; + } + + @GetMapping("/create") + public String create(Model model) { + model.addAttribute("customer", new Customer()); + return "customers/customer"; + } + + @PostMapping("/create") + public String create(String name, Model model) { + CustomerResponse customer = customerService.create(name); + model.addAttribute("customer", customer); + return "redirect:/customers/" + customer.customerId(); + } + + @GetMapping("/{customerId}") + public String findById(@PathVariable UUID customerId, Model model) { + CustomerResponse customer = customerService.findById(customerId); + model.addAttribute("customer", customer); + return "customers/customer"; + } + + @PostMapping("/update") + public String update(@Valid @ModelAttribute UpdateCustomerRequest request, Model model) { + CustomerResponse customer = customerService.update(request); + model.addAttribute("customer", customer); + return "redirect:/customers/" + customer.customerId(); + } + + @PostMapping("/{customerId}/delete") + public String deleteById(@PathVariable UUID customerId) { + customerService.deleteById(customerId); + return "customers/deleted"; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java b/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java index 22fecfa53b..9a9382d9bc 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/domain/Customer.java @@ -5,11 +5,18 @@ public class Customer { private static final int MAX_NAME_LENGTH = 25; private static final String NAME_LENGTH_EXCESSIVE = "Name is too long."; + private static final String NAME_BLANK = "Blank name is invalid."; private final UUID customerId; private final String name; private final CustomerType customerType; + public Customer() { + customerId = null; + name = null; + customerType = null; + } + public Customer(UUID customerId, String name) { this(customerId, name, CustomerType.NORMAL); } @@ -38,9 +45,20 @@ public boolean isBlack() { } private void validateCustomerName(String name) { + validateNameExcessive(name); + validateNameBlank(name); + } + + private void validateNameExcessive(String name) { if (name.length() > MAX_NAME_LENGTH) { throw new IllegalArgumentException(NAME_LENGTH_EXCESSIVE); } } + + private void validateNameBlank(String name) { + if (name.isBlank()) { + throw new IllegalArgumentException(NAME_BLANK); + } + } } diff --git a/src/main/java/com/programmers/vouchermanagement/customer/repository/CustomerRepository.java b/src/main/java/com/programmers/vouchermanagement/customer/repository/CustomerRepository.java index 4569c45372..43ca1541a9 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/repository/CustomerRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/repository/CustomerRepository.java @@ -1,16 +1,10 @@ package com.programmers.vouchermanagement.customer.repository; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; import com.programmers.vouchermanagement.customer.domain.Customer; -import com.programmers.vouchermanagement.customer.domain.CustomerType; public interface CustomerRepository { String COMMA_SEPARATOR = ", "; @@ -21,30 +15,7 @@ public interface CustomerRepository { List findBlackCustomers(); void deleteById(UUID customerId); void deleteAll(); - void loadBlacklistToStorage(); default boolean existById(UUID customerId) { return findById(customerId).isPresent(); } - default List loadBlacklist(String blacklistFilePath) { - List blacklist = new ArrayList<>(); - - try (BufferedReader br = new BufferedReader(new FileReader(blacklistFilePath))) { - br.readLine(); // skip the first line - String str; - while ((str = br.readLine()) != null) { - String[] line = str.split(COMMA_SEPARATOR); - - UUID blackCustomerId = UUID.fromString(line[0]); - String name = line[1]; - - Customer blackCustomer = new Customer(blackCustomerId, name, CustomerType.BLACK); - - blacklist.add(blackCustomer); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - return blacklist; - } } diff --git a/src/main/java/com/programmers/vouchermanagement/customer/repository/FileCustomerRepository.java b/src/main/java/com/programmers/vouchermanagement/customer/repository/FileCustomerRepository.java index 4178761606..c74d11e498 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/repository/FileCustomerRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/repository/FileCustomerRepository.java @@ -1,5 +1,9 @@ package com.programmers.vouchermanagement.customer.repository; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.UncheckedIOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -9,16 +13,18 @@ import java.util.function.Function; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Repository; +import com.programmers.vouchermanagement.configuration.profiles.FileEnabledCondition; import com.programmers.vouchermanagement.configuration.properties.file.FileProperties; import com.programmers.vouchermanagement.customer.domain.Customer; import com.programmers.vouchermanagement.customer.domain.CustomerType; import com.programmers.vouchermanagement.util.JSONFileManager; @Repository -@Profile({"file", "test"}) +@Conditional(FileEnabledCondition.class) public class FileCustomerRepository implements CustomerRepository { private static final String CUSTOMER_ID_KEY = "customerId"; private static final String NAME_KEY = "name"; @@ -35,24 +41,21 @@ public class FileCustomerRepository implements CustomerRepository { private final Function> customerToObject = (customer) -> { HashMap customerObject = new HashMap<>(); - customerObject.put("customerId", customer.getCustomerId().toString()); - customerObject.put("name", customer.getName()); - customerObject.put("customerType", customer.getCustomerType().name()); + customerObject.put(CUSTOMER_ID_KEY, customer.getCustomerId().toString()); + customerObject.put(NAME_KEY, customer.getName()); + customerObject.put(CUSTOMER_TYPE_KEY, customer.getCustomerType().name()); return customerObject; }; - private final String blacklistFilePath; private final String customerFilePath; private final JSONFileManager jsonFileManager; private final Map customers; public FileCustomerRepository(FileProperties fileProperties, @Qualifier("customer") JSONFileManager jsonFileManager) { - this.customerFilePath = fileProperties.getJSONCustomerFilePath(); - this.blacklistFilePath = fileProperties.getCSVCustomerFilePath(); + customerFilePath = fileProperties.getJSONCustomerFilePath(); this.jsonFileManager = jsonFileManager; - this.customers = new HashMap<>(); - loadCustomersFromJSON(); - loadBlacklistToStorage(); + this.customers = loadCustomersFromJSON(); + loadBlacklist(fileProperties.getCSVCustomerFilePath()); } @Override @@ -98,19 +101,30 @@ public void deleteAll() { saveFile(); } - @Override - public void loadBlacklistToStorage() { - List blacklist = loadBlacklist(blacklistFilePath); - blacklist.forEach(customer -> customers.put(customer.getCustomerId(), customer)); - - } - - private void loadCustomersFromJSON() { - List loadedCustomer = jsonFileManager.loadFile(customerFilePath, objectToCustomer); - loadedCustomer.forEach(customer -> customers.put(customer.getCustomerId(), customer)); + private Map loadCustomersFromJSON() { + return jsonFileManager.loadFile(customerFilePath, objectToCustomer, Customer::getCustomerId); } private void saveFile() { jsonFileManager.saveFile(customerFilePath, customers, customerToObject); } + + private void loadBlacklist(String blacklistFilePath) { + try (BufferedReader br = new BufferedReader(new FileReader(blacklistFilePath))) { + br.readLine(); // skip the first line + String str; + while ((str = br.readLine()) != null) { + String[] line = str.split(COMMA_SEPARATOR); + + UUID blackCustomerId = UUID.fromString(line[0]); + String name = line[1]; + + Customer blackCustomer = new Customer(blackCustomerId, name, CustomerType.BLACK); + customers.put(blackCustomerId, blackCustomer); + + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } } diff --git a/src/main/java/com/programmers/vouchermanagement/customer/repository/InMemoryCustomerRepository.java b/src/main/java/com/programmers/vouchermanagement/customer/repository/InMemoryCustomerRepository.java index 59cc55dcdf..8b1df8b107 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/repository/InMemoryCustomerRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/repository/InMemoryCustomerRepository.java @@ -1,6 +1,5 @@ package com.programmers.vouchermanagement.customer.repository; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,9 +28,6 @@ public Customer save(Customer customer) { @Override public List findAll() { - if (customers.isEmpty()) { - return Collections.emptyList(); - } return customers.values() .stream() .toList(); @@ -59,13 +55,4 @@ public void deleteById(UUID customerId) { public void deleteAll() { customers.clear(); } - - @Override - public void loadBlacklistToStorage() { - } - - @Override - public List loadBlacklist(String blacklistFilePath) { - return Collections.emptyList(); - } } diff --git a/src/main/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepository.java b/src/main/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepository.java index b1dbaee74c..bf9d3a476d 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepository.java @@ -10,30 +10,28 @@ import java.util.Optional; import java.util.UUID; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Profile; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; -import com.programmers.vouchermanagement.configuration.properties.file.FileProperties; +import com.programmers.vouchermanagement.configuration.profiles.DBEnabledCondition; import com.programmers.vouchermanagement.customer.domain.Customer; import com.programmers.vouchermanagement.customer.domain.CustomerType; import com.programmers.vouchermanagement.util.UUIDConverter; @Repository -@Profile("jdbc") +@Conditional(DBEnabledCondition.class) public class JdbcCustomerRepository implements CustomerRepository { private static final RowMapper customerRowMapper = (resultSet, i) -> mapToCustomer(resultSet); public static final int SINGLE_DATA_FLAG = 1; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; - private final String blacklistFilePath; - public JdbcCustomerRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, FileProperties fileProperties) { + public JdbcCustomerRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; - this.blacklistFilePath = fileProperties.getCSVCustomerFilePath(); - loadBlacklistToStorage(); } private static Customer mapToCustomer(ResultSet resultSet) throws SQLException { @@ -90,12 +88,6 @@ public void deleteAll() { namedParameterJdbcTemplate.update(deleteAllSQL, Collections.emptyMap()); } - @Override - public void loadBlacklistToStorage() { - List blacklist = loadBlacklist(blacklistFilePath); - blacklist.forEach(this::save); - } - private int insert(Customer customer) { String saveSQL = "INSERT INTO customers(customer_id, name, customer_type) VALUES (UUID_TO_BIN(:customerId), :name, :customerType)"; Map parameterMap = toParameterMap(customer); diff --git a/src/main/java/com/programmers/vouchermanagement/customer/service/CustomerService.java b/src/main/java/com/programmers/vouchermanagement/customer/service/CustomerService.java index 10e00c509a..8367558644 100644 --- a/src/main/java/com/programmers/vouchermanagement/customer/service/CustomerService.java +++ b/src/main/java/com/programmers/vouchermanagement/customer/service/CustomerService.java @@ -5,6 +5,7 @@ import java.util.UUID; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.programmers.vouchermanagement.customer.domain.Customer; import com.programmers.vouchermanagement.customer.dto.CustomerResponse; @@ -23,6 +24,7 @@ public CustomerService(CustomerRepository customerRepository, VoucherRepository this.voucherRepository = voucherRepository; } + @Transactional(readOnly = true) public List readBlacklist() { List blacklist = customerRepository.findBlackCustomers(); return blacklist.stream() @@ -30,12 +32,14 @@ public List readBlacklist() { .toList(); } + @Transactional public CustomerResponse create(String name) { Customer customer = new Customer(UUID.randomUUID(), name); customerRepository.save(customer); return CustomerResponse.from(customer); } + @Transactional(readOnly = true) public List findAll() { List customers = customerRepository.findAll(); return customers.stream() @@ -43,12 +47,14 @@ public List findAll() { .toList(); } + @Transactional(readOnly = true) public CustomerResponse findById(UUID customerId) { Customer customer = customerRepository.findById(customerId) .orElseThrow(() -> new NoSuchElementException("There is no customer with %s".formatted(customerId))); return CustomerResponse.from(customer); } + @Transactional public CustomerResponse update(UpdateCustomerRequest request) { validateIdExisting(request.customerId()); Customer customer = new Customer(request.customerId(), request.name(), request.customerType()); @@ -56,11 +62,13 @@ public CustomerResponse update(UpdateCustomerRequest request) { return CustomerResponse.from(updatedCustomer); } + @Transactional public void deleteById(UUID customerId) { validateIdExisting(customerId); customerRepository.deleteById(customerId); } + @Transactional(readOnly = true) public CustomerResponse findByVoucherId(UUID voucherId) { Voucher voucher = voucherRepository.findById(voucherId) .orElseThrow(() -> new NoSuchElementException(("There is no voucher with %s").formatted(voucherId))); diff --git a/src/main/java/com/programmers/vouchermanagement/util/Formatter.java b/src/main/java/com/programmers/vouchermanagement/util/Formatter.java index a5d79a1b40..b6b9533b0c 100644 --- a/src/main/java/com/programmers/vouchermanagement/util/Formatter.java +++ b/src/main/java/com/programmers/vouchermanagement/util/Formatter.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement.util; import com.programmers.vouchermanagement.customer.dto.CustomerResponse; +import com.programmers.vouchermanagement.voucher.domain.VoucherType; import com.programmers.vouchermanagement.voucher.dto.VoucherResponse; public class Formatter { @@ -19,12 +20,11 @@ private Formatter() { -------------------------"""; private static final String VOUCHER_PRESENTATION_FORMAT = """ Voucher ID : %s + Creation Datetime : %s Voucher Type : %s Discount Voucher Discount Amount : %s -------------------------"""; - //messages - public static String formatNoContent(String contentType) { return NO_CONTENT.formatted(contentType); } @@ -36,9 +36,17 @@ public static String formatCustomer(CustomerResponse customerResponse) { public static String formatVoucher(VoucherResponse voucherResponse) { return VOUCHER_PRESENTATION_FORMAT - .formatted(voucherResponse.getVoucherId(), - voucherResponse.getVoucherTypeName(), - voucherResponse.getDiscountValue() + - (voucherResponse.isPercentVoucher() ? PERCENTAGE : EMPTY)); + .formatted(voucherResponse.voucherId(), + voucherResponse.createdAt(), + voucherResponse.voucherType().displayTypeName(), + voucherResponse.discountValue() + + markPercentage(voucherResponse.voucherType())); + } + + private static String markPercentage(VoucherType voucherType) { + if (voucherType.isPercent()) { + return PERCENTAGE; + } + return EMPTY; } } diff --git a/src/main/java/com/programmers/vouchermanagement/util/JSONFileManager.java b/src/main/java/com/programmers/vouchermanagement/util/JSONFileManager.java index d365111ded..2b4a2dfc2f 100644 --- a/src/main/java/com/programmers/vouchermanagement/util/JSONFileManager.java +++ b/src/main/java/com/programmers/vouchermanagement/util/JSONFileManager.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,7 +22,7 @@ public class JSONFileManager { private static final Logger logger = LoggerFactory.getLogger(JSONFileManager.class); //messages - private static final String FILE_EXCEPTION = "Error raised while opening the file."; + private static final String FILE_EXCEPTION = "Application in illegal state. Error raised while opening the file."; private static final String IO_EXCEPTION_LOG_MESSAGE = "Error raised while reading json file."; private static final String NO_DATA_STORED = "No %s is stored yet!"; @@ -36,37 +37,44 @@ public JSONFileManager(ObjectMapper objectMapper, Class fileType) { public void saveFile(String filePath, Map objects, Function> mapDomainToObject) { try (FileWriter fileWriter = new FileWriter(filePath)) { List> targetObjects = new ArrayList<>(); - - objects.values().forEach(object -> { - HashMap targetObject = mapDomainToObject.apply(object); - targetObjects.add(targetObject); - }); - + saveTargetObject(objects, targetObjects, mapDomainToObject); String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(targetObjects); fileWriter.write(jsonStr); fileWriter.flush(); } catch (Exception e) { - throw new RuntimeException(FILE_EXCEPTION); + logger.error(FILE_EXCEPTION); + throw new IllegalStateException(FILE_EXCEPTION); } } - public List loadFile(String filePath, Function mapObjectToDomain) { + private void saveTargetObject( + Map objects, + List> targetObjects, + Function> mapDomainToObject + ) { + objects.values().forEach(object -> { + HashMap targetObject = mapDomainToObject.apply(object); + targetObjects.add(targetObject); + }); + } + + public Map loadFile(String filePath, Function mapObjectToDomain, Function keyMapper) { try { File file = new File(filePath); - Map[] voucherObjects = objectMapper.readValue(file, Map[].class); - return loadTargets(voucherObjects, mapObjectToDomain); + Map[] targetObjects = objectMapper.readValue(file, Map[].class); + return loadTargets(targetObjects, mapObjectToDomain, keyMapper); } catch (MismatchedInputException e) { logger.debug(NO_DATA_STORED.formatted(fileType.getCanonicalName())); - return new ArrayList<>(); + return new HashMap<>(); } catch (IOException e) { logger.error(IO_EXCEPTION_LOG_MESSAGE); throw new UncheckedIOException(e); } } - private List loadTargets(Map[] targetObjects, Function mapObjectToDomain) { + private Map loadTargets(Map[] targetObjects, Function mapObjectToDomain, Function keyMapper) { return Arrays.stream(targetObjects) .map(mapObjectToDomain) - .toList(); + .collect(Collectors.toMap(keyMapper, domain -> domain)); } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherAdminController.java b/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherAdminController.java new file mode 100644 index 0000000000..912f3a02d8 --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherAdminController.java @@ -0,0 +1,72 @@ +package com.programmers.vouchermanagement.voucher.controller; + +import java.util.List; +import java.util.UUID; + +import jakarta.validation.Valid; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +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.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +import com.programmers.vouchermanagement.advice.annotation.AdminController; +import com.programmers.vouchermanagement.voucher.domain.Voucher; +import com.programmers.vouchermanagement.voucher.dto.CreateVoucherRequest; +import com.programmers.vouchermanagement.voucher.dto.UpdateVoucherRequest; +import com.programmers.vouchermanagement.voucher.dto.VoucherResponse; +import com.programmers.vouchermanagement.voucher.service.VoucherService; + +@AdminController +@ConditionalOnWebApplication +@RequestMapping("/vouchers") +public class VoucherAdminController { + private final VoucherService voucherService; + + public VoucherAdminController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping + public String readAllVouchers(Model model) { + List vouchers = voucherService.readAllVouchers(); + model.addAttribute("vouchers", vouchers); + return "vouchers/vouchers"; + } + + @GetMapping("/create") + public String create(Model model) { + model.addAttribute("voucher", new Voucher()); + return "vouchers/voucher"; + } + + @PostMapping("/create") + public String create(@Valid @ModelAttribute CreateVoucherRequest request, Model model) { + VoucherResponse voucher = voucherService.create(request); + model.addAttribute("voucher", voucher); + return "redirect:/vouchers/" + voucher.voucherId(); + } + + @GetMapping("/{voucherId}") + public String findById(@PathVariable UUID voucherId, Model model) { + VoucherResponse voucher = voucherService.findById(voucherId); + model.addAttribute("voucher", voucher); + return "vouchers/voucher"; + } + + @PostMapping("/update") + public String update(@Valid @ModelAttribute UpdateVoucherRequest request, Model model) { + VoucherResponse voucher = voucherService.update(request); + model.addAttribute("voucher", voucher); + return "redirect:/vouchers/" + voucher.voucherId(); + } + + @PostMapping("/{voucherId}/delete") + public String deleteById(@PathVariable UUID voucherId) { + voucherService.deleteById(voucherId); + return "vouchers/deleted"; + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherController.java b/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherController.java index 4e9f4fed75..7c467f8900 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherController.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherController.java @@ -1,14 +1,14 @@ package com.programmers.vouchermanagement.voucher.controller; -import org.springframework.stereotype.Controller; - import java.util.List; import java.util.UUID; +import org.springframework.stereotype.Controller; + import com.programmers.vouchermanagement.consoleapp.io.ConsoleManager; -import com.programmers.vouchermanagement.voucher.dto.VoucherCustomerRequest; import com.programmers.vouchermanagement.voucher.dto.CreateVoucherRequest; import com.programmers.vouchermanagement.voucher.dto.UpdateVoucherRequest; +import com.programmers.vouchermanagement.voucher.dto.VoucherCustomerRequest; import com.programmers.vouchermanagement.voucher.dto.VoucherResponse; import com.programmers.vouchermanagement.voucher.service.VoucherService; diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherRestController.java b/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherRestController.java new file mode 100644 index 0000000000..86b0c11bdb --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/controller/VoucherRestController.java @@ -0,0 +1,85 @@ +package com.programmers.vouchermanagement.voucher.controller; + +import java.time.LocalDate; +import java.util.List; +import java.util.UUID; + +import jakarta.validation.Valid; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.programmers.vouchermanagement.voucher.domain.VoucherType; +import com.programmers.vouchermanagement.voucher.dto.CreateVoucherRequest; +import com.programmers.vouchermanagement.voucher.dto.SearchCreatedAtRequest; +import com.programmers.vouchermanagement.voucher.dto.VoucherResponse; +import com.programmers.vouchermanagement.voucher.service.VoucherService; + +@RestController +@ConditionalOnWebApplication +@RequestMapping(value = "/api/v1/vouchers", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}) +public class VoucherRestController { + private final VoucherService voucherService; + + public VoucherRestController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public VoucherResponse create(@Valid CreateVoucherRequest createVoucherRequest) { + return voucherService.create(createVoucherRequest); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List readAllVouchers(String typeName, LocalDate startDate, LocalDate endDate) { + if (isSearchingType(typeName)) { + return findByType(typeName); + } + + if (isSearchingDate(startDate, endDate)) { + return findByCreatedAt(startDate, endDate); + } + + return voucherService.readAllVouchers(); + } + + @GetMapping("/{voucherId}") + @ResponseStatus(HttpStatus.OK) + public VoucherResponse findById(@PathVariable UUID voucherId) { + return voucherService.findById(voucherId); + } + + @DeleteMapping("/{voucherId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteById(@PathVariable UUID voucherId) { + voucherService.deleteById(voucherId); + } + + private boolean isSearchingType(String typeName) { + return typeName != null; + } + + private boolean isSearchingDate(LocalDate startDate, LocalDate endDate) { + return startDate != null && endDate != null; + } + + private List findByType(String typeName) { + VoucherType voucherType = VoucherType.findVoucherType(typeName); + return voucherService.findByType(voucherType); + } + + private List findByCreatedAt(LocalDate startDate, LocalDate endDate) { + SearchCreatedAtRequest request = new SearchCreatedAtRequest(startDate, endDate); + return voucherService.findByCreatedAt(request); + } +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java index 61af09a92a..695749ebb9 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/Voucher.java @@ -1,21 +1,36 @@ package com.programmers.vouchermanagement.voucher.domain; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.UUID; public class Voucher { private final UUID voucherId; + private final LocalDateTime createdAt; private final BigDecimal discountValue; private final VoucherType voucherType; private final UUID customerId; + public Voucher() { + this.voucherId = null; + this.createdAt = null; + this.discountValue = null; + this.voucherType = null; + this.customerId = null; + } + public Voucher(UUID voucherId, BigDecimal discountValue, VoucherType voucherType) { - this(voucherId, discountValue, voucherType, null); + this(voucherId, LocalDateTime.now(), discountValue, voucherType, null); } public Voucher(UUID voucherId, BigDecimal discountValue, VoucherType voucherType, UUID customerId) { + this(voucherId, LocalDateTime.now(), discountValue, voucherType, customerId); + } + + public Voucher(UUID voucherId, LocalDateTime createdAt, BigDecimal discountValue, VoucherType voucherType, UUID customerId) { voucherType.validateDiscountValue(discountValue); this.voucherId = voucherId; + this.createdAt = createdAt; this.voucherType = voucherType; this.discountValue = discountValue; this.customerId = customerId; @@ -25,6 +40,10 @@ public UUID getVoucherId() { return voucherId; } + public LocalDateTime getCreatedAt() { + return createdAt; + } + public BigDecimal getDiscountValue() { return discountValue; } @@ -40,4 +59,12 @@ public UUID getCustomerId() { public boolean isOwned() { return this.customerId != null; } + + public boolean isSameType(VoucherType voucherType) { + return this.voucherType == voucherType; + } + + public boolean isCreatedInBetween(LocalDateTime startDateTime, LocalDateTime endDateTime) { + return !createdAt.isBefore(startDateTime) && !createdAt.isAfter(endDateTime); + } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherType.java b/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherType.java index 44ceb493f1..566b25161c 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherType.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/domain/VoucherType.java @@ -34,7 +34,15 @@ public enum VoucherType { this.validator = validator; } - public static VoucherType findVoucherTypeByName(String input) { + public static VoucherType findVoucherType(String input) { + try { + return VoucherType.findVoucherTypeByCode(input); + } catch (IllegalArgumentException exception) { + return VoucherType.findVoucherTypeByName(input); + } + } + + private static VoucherType findVoucherTypeByName(String input) { return Arrays.stream(VoucherType.values()) .filter(menu -> menu.isMatchingName(input)) .findFirst() @@ -44,14 +52,11 @@ public static VoucherType findVoucherTypeByName(String input) { }); } - public static VoucherType findVoucherTypeByCode(String input) { + private static VoucherType findVoucherTypeByCode(String input) { return Arrays.stream(VoucherType.values()) .filter(menu -> menu.isMatchingCode(input)) .findFirst() - .orElseThrow(() -> { - logger.error(INVALID_VOUCHER_TYPE_MESSAGE); - return new IllegalArgumentException(INVALID_VOUCHER_TYPE_MESSAGE); - }); + .orElseThrow(IllegalArgumentException::new); } private boolean isMatchingName(String input) { diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/CreateVoucherRequest.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/CreateVoucherRequest.java index 69f62dea49..be3d77b9fb 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/dto/CreateVoucherRequest.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/CreateVoucherRequest.java @@ -1,8 +1,10 @@ package com.programmers.vouchermanagement.voucher.dto; -import java.math.BigDecimal; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; -import com.programmers.vouchermanagement.voucher.domain.VoucherType; - -public record CreateVoucherRequest(BigDecimal discountValue, VoucherType voucherType) { +public record CreateVoucherRequest( + @Positive(message = "Discount value must be greater than 0.") long discountValue, + @NotBlank(message = "Voucher type cannot be blank.") String voucherType +) { } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/SearchCreatedAtRequest.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/SearchCreatedAtRequest.java new file mode 100644 index 0000000000..0f2e7b0c8b --- /dev/null +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/SearchCreatedAtRequest.java @@ -0,0 +1,6 @@ +package com.programmers.vouchermanagement.voucher.dto; + +import java.time.LocalDate; + +public record SearchCreatedAtRequest(LocalDate startDate, LocalDate endDate) { +} diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/UpdateVoucherRequest.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/UpdateVoucherRequest.java index fd4c133792..bd73fb74f0 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/dto/UpdateVoucherRequest.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/UpdateVoucherRequest.java @@ -1,9 +1,12 @@ package com.programmers.vouchermanagement.voucher.dto; -import java.math.BigDecimal; import java.util.UUID; -import com.programmers.vouchermanagement.voucher.domain.VoucherType; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; -public record UpdateVoucherRequest(UUID voucherId, BigDecimal discountValue, VoucherType voucherType) { +public record UpdateVoucherRequest( + UUID voucherId, + @Min(value = 0, message = "Discount value must be greater than 0.") long discountValue, + @NotBlank(message = "Voucher type cannot be blank.") String voucherType) { } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherResponse.java b/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherResponse.java index 0a14338d04..1cb91ebda2 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherResponse.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/dto/VoucherResponse.java @@ -1,49 +1,26 @@ package com.programmers.vouchermanagement.voucher.dto; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.UUID; import com.programmers.vouchermanagement.voucher.domain.Voucher; import com.programmers.vouchermanagement.voucher.domain.VoucherType; -public class VoucherResponse { - private final UUID voucherId; - private final BigDecimal discountValue; - private final VoucherType voucherType; - private final UUID customerId; - - private VoucherResponse(UUID voucherId, BigDecimal discountValue, VoucherType voucherType, UUID customerId) { - this.voucherId = voucherId; - this.discountValue = discountValue; - this.voucherType = voucherType; - this.customerId = customerId; - } - +public record VoucherResponse( + UUID voucherId, + LocalDateTime createdAt, + BigDecimal discountValue, + VoucherType voucherType, + UUID customerId +) { public static VoucherResponse from(Voucher voucher) { - return new VoucherResponse(voucher.getVoucherId(), voucher.getDiscountValue(), voucher.getVoucherType(), voucher.getCustomerId()); - } - - public UUID getVoucherId() { - return voucherId; - } - - public BigDecimal getDiscountValue() { - return discountValue; - } - - public UUID getCustomerId() { - return customerId; - } - - public VoucherType getVoucherType() { - return voucherType; - } - - public boolean isPercentVoucher() { - return voucherType.isPercent(); - } - - public String getVoucherTypeName() { - return voucherType.displayTypeName(); + return new VoucherResponse( + voucher.getVoucherId(), + voucher.getCreatedAt(), + voucher.getDiscountValue(), + voucher.getVoucherType(), + voucher.getCustomerId() + ); } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/repository/FileVoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/repository/FileVoucherRepository.java index 0648c92c3a..35238261eb 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/repository/FileVoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/repository/FileVoucherRepository.java @@ -1,6 +1,7 @@ package com.programmers.vouchermanagement.voucher.repository; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -10,19 +11,22 @@ import java.util.function.Function; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Repository; +import com.programmers.vouchermanagement.configuration.profiles.FileEnabledCondition; import com.programmers.vouchermanagement.configuration.properties.file.FileProperties; import com.programmers.vouchermanagement.util.JSONFileManager; import com.programmers.vouchermanagement.voucher.domain.Voucher; import com.programmers.vouchermanagement.voucher.domain.VoucherType; @Repository -@Profile({"file", "test"}) +@Conditional(FileEnabledCondition.class) public class FileVoucherRepository implements VoucherRepository { //constants private static final String VOUCHER_ID_KEY = "voucher_id"; + private static final String VOUCHER_CREATED_AT_KEY = "created_at"; private static final String DISCOUNT_VALUE_KEY = "discount_value"; private static final String VOUCHER_TYPE_KEY = "voucher_type"; private static final String CUSTOMER_ID_KEY = "customer_id"; @@ -33,16 +37,18 @@ public class FileVoucherRepository implements VoucherRepository { private final Function objectToVoucher = (voucherObject) -> { UUID voucherId = UUID.fromString(String.valueOf(voucherObject.get(VOUCHER_ID_KEY))); + LocalDateTime createdAt = LocalDateTime.parse(String.valueOf(voucherObject.get(VOUCHER_CREATED_AT_KEY))); BigDecimal discountValue = new BigDecimal(String.valueOf(voucherObject.get(DISCOUNT_VALUE_KEY))); String voucherTypeName = String.valueOf(voucherObject.get(VOUCHER_TYPE_KEY)); - VoucherType voucherType = VoucherType.findVoucherTypeByName(voucherTypeName); + VoucherType voucherType = VoucherType.findVoucherType(voucherTypeName); String customerIdString = String.valueOf(voucherObject.get(CUSTOMER_ID_KEY)); UUID customerId = customerIdString.equals("null") ? null : UUID.fromString(customerIdString); - return new Voucher(voucherId, discountValue, voucherType, customerId); + return new Voucher(voucherId, createdAt, discountValue, voucherType, customerId); }; private final Function> voucherToObject = (voucher) -> { HashMap voucherObject = new HashMap<>(); voucherObject.put(VOUCHER_ID_KEY, voucher.getVoucherId().toString()); + voucherObject.put(VOUCHER_CREATED_AT_KEY, voucher.getCreatedAt().toString()); voucherObject.put(DISCOUNT_VALUE_KEY, voucher.getDiscountValue().toString()); voucherObject.put(VOUCHER_TYPE_KEY, voucher.getVoucherType().name()); voucherObject.put(CUSTOMER_ID_KEY, voucher.getCustomerId()); @@ -52,8 +58,7 @@ public class FileVoucherRepository implements VoucherRepository { public FileVoucherRepository(FileProperties fileProperties, @Qualifier("voucher") JSONFileManager jsonFileManager) { this.filePath = fileProperties.getVoucherFilePath(); this.jsonFileManager = jsonFileManager; - this.vouchers = new HashMap<>(); - loadVouchersFromJSON(); + this.vouchers = loadVouchersFromJSON(); } @Override @@ -70,6 +75,22 @@ public List findAll() { .toList(); } + @Override + public List findByType(VoucherType voucherType) { + return vouchers.values() + .stream() + .filter(voucher -> voucher.isSameType(voucherType)) + .toList(); + } + + @Override + public List findByCreatedAt(LocalDateTime startDateTime, LocalDateTime endDateTime) { + return vouchers.values() + .stream() + .filter(voucher -> voucher.isCreatedInBetween(startDateTime, endDateTime)) + .toList(); + } + @Override public Optional findById(UUID voucherId) { return Optional.ofNullable(vouchers.get(voucherId)); @@ -94,9 +115,8 @@ public void deleteAll() { vouchers.clear(); } - private void loadVouchersFromJSON() { - List loadedVouchers = jsonFileManager.loadFile(filePath, objectToVoucher); - loadedVouchers.forEach(voucher -> vouchers.put(voucher.getVoucherId(), voucher)); + private Map loadVouchersFromJSON() { + return jsonFileManager.loadFile(filePath, objectToVoucher, Voucher::getVoucherId); } private void saveFile() { diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/repository/InMemoryVoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/repository/InMemoryVoucherRepository.java index 4350975b84..f98dc668a6 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/repository/InMemoryVoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/repository/InMemoryVoucherRepository.java @@ -1,5 +1,6 @@ package com.programmers.vouchermanagement.voucher.repository; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -11,6 +12,7 @@ import org.springframework.stereotype.Repository; import com.programmers.vouchermanagement.voucher.domain.Voucher; +import com.programmers.vouchermanagement.voucher.domain.VoucherType; @Repository @Profile("dev") @@ -32,6 +34,22 @@ public List findAll() { return vouchers.values().stream().toList(); } + @Override + public List findByType(VoucherType voucherType) { + return vouchers.values() + .stream() + .filter(voucher -> voucher.isSameType(voucherType)) + .toList(); + } + + @Override + public List findByCreatedAt(LocalDateTime startDateTime, LocalDateTime endDateTime) { + return vouchers.values() + .stream() + .filter(voucher -> voucher.isCreatedInBetween(startDateTime, endDateTime)) + .toList(); + } + @Override public Optional findById(UUID voucherId) { return Optional.ofNullable(vouchers.get(voucherId)); diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepository.java index 083fb2f2f4..6829d6c68a 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepository.java @@ -3,6 +3,8 @@ import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -11,18 +13,20 @@ import java.util.Optional; import java.util.UUID; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Profile; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; +import com.programmers.vouchermanagement.configuration.profiles.DBEnabledCondition; import com.programmers.vouchermanagement.util.UUIDConverter; import com.programmers.vouchermanagement.voucher.domain.Voucher; import com.programmers.vouchermanagement.voucher.domain.VoucherType; @Repository -@Profile("jdbc") +@Conditional(DBEnabledCondition.class) public class JdbcVoucherRepository implements VoucherRepository { private static final int SINGLE_DATA_FLAG = 1; @@ -49,6 +53,22 @@ public List findAll() { return Collections.unmodifiableList(namedParameterJdbcTemplate.query(findAllSQL, voucherRowMapper)); } + @Override + public List findByType(VoucherType voucherType) { + String findByTypeSQL = "SELECT * FROM vouchers WHERE voucher_type=(:voucherType)"; + Map parameterMap = Collections.singletonMap("voucherType", voucherType.name()); + return namedParameterJdbcTemplate.query(findByTypeSQL, parameterMap, voucherRowMapper); + } + + @Override + public List findByCreatedAt(LocalDateTime startDateTime, LocalDateTime endDateTime) { + String findByCreatedAtSQL = "SELECT * FROM vouchers WHERE created_at BETWEEN :startDate AND :endDate"; + Map parameterMap = new HashMap<>(); + parameterMap.put("startDate", startDateTime); + parameterMap.put("endDate", endDateTime); + return namedParameterJdbcTemplate.query(findByCreatedAtSQL, Collections.unmodifiableMap(parameterMap), voucherRowMapper); + } + @Override public Optional findById(UUID voucherId) { String findByIdSQL = "SELECT * FROM vouchers WHERE voucher_id=UUID_TO_BIN(:voucherId)"; @@ -77,10 +97,12 @@ public void deleteById(UUID voucherId) { @Override @Profile("test") //clarify that this is for testing only public void deleteAll() { + String deleteSQL = "DELETE FROM vouchers"; + namedParameterJdbcTemplate.update(deleteSQL, Collections.emptyMap()); } private int insert(Voucher voucher) { - String saveSQL = "INSERT INTO vouchers(voucher_id, discount_value, voucher_type) VALUES (UUID_TO_BIN(:voucherId), :discountValue, :voucherType)"; + String saveSQL = "INSERT INTO vouchers(voucher_id, created_at, discount_value, voucher_type) VALUES (UUID_TO_BIN(:voucherId), :createdAt, :discountValue, :voucherType)"; Map parameterMap = toParameterMap(voucher); return namedParameterJdbcTemplate.update(saveSQL, parameterMap); } @@ -94,19 +116,22 @@ private int update(Voucher voucher) { private Map toParameterMap(Voucher voucher) { Map parameterMap = new HashMap<>(); parameterMap.put("voucherId", voucher.getVoucherId().toString().getBytes()); + parameterMap.put("createdAt", Timestamp.valueOf(voucher.getCreatedAt())); parameterMap.put("discountValue", voucher.getDiscountValue()); parameterMap.put("voucherType", voucher.getVoucherType().name()); + parameterMap.put("customerId", voucher.getCustomerId()); return Collections.unmodifiableMap(parameterMap); } private static Voucher mapToVoucher(ResultSet resultSet) throws SQLException { final UUID voucherId = UUIDConverter.from(resultSet.getBytes("voucher_id")); + final LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime(); final BigDecimal discountValue = resultSet.getBigDecimal("discount_value"); final String voucherTypeName = resultSet.getString("voucher_type"); - final VoucherType voucherType = VoucherType.findVoucherTypeByName(voucherTypeName); + final VoucherType voucherType = VoucherType.findVoucherType(voucherTypeName); byte[] idBytes = resultSet.getBytes("customer_id"); final UUID customerId = idBytes != null ? UUIDConverter.from(idBytes) : null; - return new Voucher(voucherId, discountValue, voucherType, customerId); + return new Voucher(voucherId, createdAt, discountValue, voucherType, customerId); } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/repository/VoucherRepository.java b/src/main/java/com/programmers/vouchermanagement/voucher/repository/VoucherRepository.java index 542e0411d6..c2617690be 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/repository/VoucherRepository.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/repository/VoucherRepository.java @@ -1,19 +1,23 @@ package com.programmers.vouchermanagement.voucher.repository; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; import com.programmers.vouchermanagement.voucher.domain.Voucher; +import com.programmers.vouchermanagement.voucher.domain.VoucherType; public interface VoucherRepository { Voucher save(Voucher voucher); List findAll(); + List findByType(VoucherType voucherType); + List findByCreatedAt(LocalDateTime startDateTime, LocalDateTime endDateTime); Optional findById(UUID voucherId); List findByCustomerId(UUID customerId); void deleteById(UUID voucherId); void deleteAll(); default boolean existById(UUID voucherId) { return findById(voucherId).isPresent(); - }; + } } diff --git a/src/main/java/com/programmers/vouchermanagement/voucher/service/VoucherService.java b/src/main/java/com/programmers/vouchermanagement/voucher/service/VoucherService.java index 71d473e80b..0fc438bf11 100644 --- a/src/main/java/com/programmers/vouchermanagement/voucher/service/VoucherService.java +++ b/src/main/java/com/programmers/vouchermanagement/voucher/service/VoucherService.java @@ -1,15 +1,21 @@ package com.programmers.vouchermanagement.voucher.service; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; import java.util.NoSuchElementException; import java.util.UUID; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.programmers.vouchermanagement.customer.domain.Customer; import com.programmers.vouchermanagement.customer.repository.CustomerRepository; import com.programmers.vouchermanagement.voucher.domain.Voucher; +import com.programmers.vouchermanagement.voucher.domain.VoucherType; import com.programmers.vouchermanagement.voucher.dto.CreateVoucherRequest; +import com.programmers.vouchermanagement.voucher.dto.SearchCreatedAtRequest; import com.programmers.vouchermanagement.voucher.dto.UpdateVoucherRequest; import com.programmers.vouchermanagement.voucher.dto.VoucherCustomerRequest; import com.programmers.vouchermanagement.voucher.dto.VoucherResponse; @@ -25,12 +31,15 @@ public VoucherService(VoucherRepository voucherRepository, CustomerRepository cu this.customerRepository = customerRepository; } + @Transactional public VoucherResponse create(CreateVoucherRequest request) { - Voucher voucher = new Voucher(UUID.randomUUID(), request.discountValue(), request.voucherType()); + VoucherType voucherType = VoucherType.findVoucherType(request.voucherType()); + Voucher voucher = new Voucher(UUID.randomUUID(), new BigDecimal(request.discountValue()), voucherType); voucherRepository.save(voucher); return VoucherResponse.from(voucher); } + @Transactional(readOnly = true) public List readAllVouchers() { List vouchers = voucherRepository.findAll(); return vouchers.stream() @@ -38,38 +47,76 @@ public List readAllVouchers() { .toList(); } + @Transactional(readOnly = true) + public List findByType(VoucherType voucherType) { + List vouchers = voucherRepository.findByType(voucherType); + return vouchers.stream() + .map(VoucherResponse::from) + .toList(); + } + + @Transactional(readOnly = true) + public List findByCreatedAt(SearchCreatedAtRequest request) { + validateDateRange(request); + LocalDateTime startDateTime = request.startDate().atStartOfDay(); + LocalDateTime endDateTime = request.endDate().atTime(LocalTime.MAX); + List vouchers = voucherRepository.findByCreatedAt(startDateTime, endDateTime); + return vouchers.stream() + .map(VoucherResponse::from) + .toList(); + } + + @Transactional(readOnly = true) public VoucherResponse findById(UUID voucherId) { Voucher voucher = voucherRepository.findById(voucherId) .orElseThrow(() -> new NoSuchElementException("There is no voucher with %s".formatted(voucherId))); return VoucherResponse.from(voucher); } + @Transactional public VoucherResponse update(UpdateVoucherRequest request) { validateVoucherIdExisting(request.voucherId()); - Voucher voucher = new Voucher(request.voucherId(), request.discountValue(), request.voucherType()); + VoucherType voucherType = VoucherType.findVoucherType(request.voucherType()); + Voucher voucher = new Voucher(request.voucherId(), new BigDecimal(request.discountValue()), voucherType); Voucher updatedVoucher = voucherRepository.save(voucher); return VoucherResponse.from(updatedVoucher); } + @Transactional public void deleteById(UUID voucherId) { validateVoucherIdExisting(voucherId); voucherRepository.deleteById(voucherId); } + @Transactional public void grantToCustomer(VoucherCustomerRequest request) { validateCustomerIdExisting(request.customerId()); VoucherResponse foundVoucher = findById(request.voucherId()); - Voucher voucher = new Voucher(request.voucherId(), foundVoucher.getDiscountValue(), foundVoucher.getVoucherType(), request.customerId()); + Voucher voucher = new Voucher( + request.voucherId(), + foundVoucher.createdAt(), + foundVoucher.discountValue(), + foundVoucher.voucherType(), + request.customerId() + ); voucherRepository.save(voucher); } + @Transactional public void releaseFromCustomer(VoucherCustomerRequest request) { validateCustomerIdExisting(request.customerId()); VoucherResponse foundVoucher = findById(request.voucherId()); - Voucher voucher = new Voucher(foundVoucher.getVoucherId(), foundVoucher.getDiscountValue(), foundVoucher.getVoucherType()); + Voucher voucher = new Voucher( + foundVoucher.voucherId(), + foundVoucher.createdAt(), + foundVoucher.discountValue(), + foundVoucher.voucherType(), + null + ); voucherRepository.save(voucher); } + @Transactional(readOnly = true) public List findByCustomerId(UUID customerId) { Customer customer = customerRepository.findById(customerId) .orElseThrow(() -> new NoSuchElementException("There is no customer with %s".formatted(customerId))); @@ -91,4 +138,10 @@ private void validateCustomerIdExisting(UUID customerId) { throw new NoSuchElementException("There is no customer with %s".formatted(customerId)); } } + + private void validateDateRange(SearchCreatedAtRequest request) { + if (request.endDate().isBefore(request.startDate())) { + throw new IllegalArgumentException("The end date should be at least equal to the start date."); + } + } } diff --git a/src/main/resources/application-jdbc.yaml b/src/main/resources/application.yaml similarity index 77% rename from src/main/resources/application-jdbc.yaml rename to src/main/resources/application.yaml index 6b76a45a4f..c75bca85e5 100644 --- a/src/main/resources/application-jdbc.yaml +++ b/src/main/resources/application.yaml @@ -1,5 +1,5 @@ datasource: - url: jdbc:mysql://localhost:3305/voucher_management + url: jdbc:mysql://localhost:33060/voucher_management username: root password: 1234 diff --git a/src/main/resources/static/styles/common.css b/src/main/resources/static/styles/common.css new file mode 100644 index 0000000000..5c2a7d9db9 --- /dev/null +++ b/src/main/resources/static/styles/common.css @@ -0,0 +1,37 @@ +@import url('https://fonts.googleapis.com/css2?family=Anton&family=Playpen+Sans:wght@400;600&display=swap'); + +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 #eaf6f6; } +} + +body { + box-sizing: border-box; + font-family: 'Playpen Sans', cursive; + max-width: 1180px; + margin: 0 auto; +} + +button { + font-family: 'Playpen Sans', cursive; + padding: 1rem 0.5rem; + background-color: #ccf5f5; + border: 2px solid #ccf5f5; + border-radius: 10px; + margin-right: 0.5rem; + transition: transform 0.5s ease; + color: #556175; + cursor: pointer; + &:hover { + animation: pulse 1s; + box-shadow: 0 0 0 50px transparent; + transform: translateY(-0.4rem) scale(1.05); + font-weight: 600; + color: black; + } +} + +a { + width: 100%; + height: 100%; + text-decoration-line: none; +} diff --git a/src/main/resources/static/styles/detail.css b/src/main/resources/static/styles/detail.css new file mode 100644 index 0000000000..ddb75d9b63 --- /dev/null +++ b/src/main/resources/static/styles/detail.css @@ -0,0 +1,53 @@ +.voucher-detail-container header, .customer-detail-container header { + display: flex; + justify-content: center; + align-items: center; + margin: 4rem; + font-size: 1.4rem; +} + +.back-to-list-button { + padding: 1.5rem 0.7rem; + font-weight: 600; +} + +.customer-detail-box-wrapper, .voucher-detail-box-wrapper { + max-width: 760px; + margin: 0 auto; + border: 3px solid #556175; + border-radius: 10px; +} + +.customer-detail-box, .voucher-detail-box { + width: 80%; + margin: 1.5rem auto; +} + +.customer-detail-form, .voucher-detail-form { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +.customer-detail-form div, .voucher-detail-form div { + width: 70%; + margin: 0 auto; + display: flex; + justify-content: space-between; +} + +.customer-detail-form .button-wrapper, .voucher-detail-form .button-wrapper { + display: flex; + justify-content: center; + margin-bottom: 1.5rem; +} + +.delete-form { + display: flex; + justify-content: center; +} + +.delete-form button { + background-color: #e53c3c; + border: 2px solid #e53c3c; +} diff --git a/src/main/resources/static/styles/index.css b/src/main/resources/static/styles/index.css new file mode 100644 index 0000000000..cd9b4ab07b --- /dev/null +++ b/src/main/resources/static/styles/index.css @@ -0,0 +1,23 @@ +.main-container { + text-align: center; +} + +.admin-header { + margin: 5rem; + font-size: 2rem; +} + +.sub-header { + font-size: 1.5rem; + padding: 2rem 0; +} + +.menu-container { + width: 40%; + margin: 0 auto; +} + +.menu-wrapper { + display: flex; + justify-content: space-between; +} diff --git a/src/main/resources/static/styles/list.css b/src/main/resources/static/styles/list.css new file mode 100644 index 0000000000..e36b3deccf --- /dev/null +++ b/src/main/resources/static/styles/list.css @@ -0,0 +1,79 @@ +.voucher-page-container, .customer-page-container { +} + +.voucher-page-container header, .customer-page-container header { + display: flex; + justify-content: center; + align-items: center; + margin: 4rem; + font-size: 1.4rem; +} + +.vouchers-container, .customers-container { + font-size: 1.1rem; +} + +.voucher-item-header, .customer-item-header { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 600; + border-bottom: 2px solid #383841; + padding-bottom: 0.7rem; + margin-bottom: 1.5rem; +} + +.voucher-item-description, .customer-item-description, .voucher-item-detail, .customer-item-detail { + flex: 1; + display: flex; +} + +.voucher-id, .customer-id { + flex: 2 1 0; + text-align: center; +} + +.created-at { + flex: 1 1 0; +} + +.discount-value, .customer-name { + flex: 1 1 0; + text-align: center; +} + +.voucher-type, .customer-type{ + flex: 1 1 0; + text-align: center; +} + +.voucher-items, .customer-items { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.voucher-item, .customer-item { + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #555562; + padding-bottom: 0.7rem; +} + +.voucher-item-detail { + margin-right: 1rem; +} + +.customer-item-detail { + margin-right: 1.3rem; +} + +.voucher-detail-btn, .customer-detail-btn { + padding: 1.3rem 0.5rem; +} + +.back-to-home-button { + padding: 1.5rem 0.7rem; + font-weight: 600; +} diff --git a/src/main/resources/templates/customers/customer.html b/src/main/resources/templates/customers/customer.html new file mode 100644 index 0000000000..260c002ce6 --- /dev/null +++ b/src/main/resources/templates/customers/customer.html @@ -0,0 +1,92 @@ + + + + + + + + + customer Management Admin - Customer Detail + + +
+
+

+
+
+
+ +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ This customer is + + + + customer. +
+
+ + +
+
+ +
+
+
+ +
+
+
+
+
+ + diff --git a/src/main/resources/templates/customers/customers.html b/src/main/resources/templates/customers/customers.html new file mode 100644 index 0000000000..d9af0c50be --- /dev/null +++ b/src/main/resources/templates/customers/customers.html @@ -0,0 +1,57 @@ + + + + + + + + + customer Management Admin - Customers + + +
+
+

All Customers

+
+
+ +
+
+
+
+ Customer ID + Customer Name + Customer Type +
+ +
+
+
+
+ + + +
+ +
+
+
+
+ + diff --git a/src/main/resources/templates/customers/deleted.html b/src/main/resources/templates/customers/deleted.html new file mode 100644 index 0000000000..724899efdc --- /dev/null +++ b/src/main/resources/templates/customers/deleted.html @@ -0,0 +1,21 @@ + + + + + + + + Voucher Management Admin - Deleted + + +
+

Customer has been successfully deleted!

+
+
+ +
+ + diff --git a/src/main/resources/templates/error.html b/src/main/resources/templates/error.html new file mode 100644 index 0000000000..f5281f7f2d --- /dev/null +++ b/src/main/resources/templates/error.html @@ -0,0 +1,21 @@ + + + + + + + + Voucher Management Admin - Error + + +
+

Error Happened!

+
+
+ +
+ + diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000000..d4f9274739 --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,35 @@ + + + + + + + + + + Voucher Management Admin + + +
+

Voucher Management Admin

+
+
+

Select Service Menu

+
+ + + diff --git a/src/main/resources/templates/vouchers/deleted.html b/src/main/resources/templates/vouchers/deleted.html new file mode 100644 index 0000000000..1041d02ddd --- /dev/null +++ b/src/main/resources/templates/vouchers/deleted.html @@ -0,0 +1,21 @@ + + + + + + + + Voucher Management Admin - Deleted + + +
+

Voucher has been successfully deleted!

+
+
+ +
+ + diff --git a/src/main/resources/templates/vouchers/voucher.html b/src/main/resources/templates/vouchers/voucher.html new file mode 100644 index 0000000000..6a1ab2e9bb --- /dev/null +++ b/src/main/resources/templates/vouchers/voucher.html @@ -0,0 +1,93 @@ + + + + + + + + + Voucher Management Admin - Voucher Detail + + +
+
+

+
+
+ +
+
+
+
+ + +
+
+ Creation Datetime : +
+
+ + +
+
+ + +
+
+ Current Voucher Type : + + + + Discount Voucher +
+
+ + +
+
+ +
+
+
+ +
+
+
+
+
+ + diff --git a/src/main/resources/templates/vouchers/vouchers.html b/src/main/resources/templates/vouchers/vouchers.html new file mode 100644 index 0000000000..0363903dbf --- /dev/null +++ b/src/main/resources/templates/vouchers/vouchers.html @@ -0,0 +1,59 @@ + + + + + + + + + Voucher Management Admin - Vouchers + + +
+
+

All Vouchers

+
+
+ +
+
+
+
+ Voucher ID + Creation Datetime + Discount Value + Voucher Type +
+ +
+
+
+
+ + + + +
+ +
+
+
+
+ + diff --git a/src/main/resources/voucher.json b/src/main/resources/voucher.json index f3e4bd30db..e69de29bb2 100644 --- a/src/main/resources/voucher.json +++ b/src/main/resources/voucher.json @@ -1,11 +0,0 @@ -[ { - "voucher_type" : "FIXED", - "voucher_id" : "4b10670f-5fcc-4b05-ba9c-c7972411c1fb", - "discount_value" : "1000", - "customer_id" : null -}, { - "voucher_type" : "FIXED", - "voucher_id" : "4580ed30-4725-481a-807b-feeeccd5cd06", - "discount_value" : "10000", - "customer_id" : null -} ] diff --git a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/CustomerMenuTest.java b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/CustomerMenuTest.java index cd4af1231f..22cfc8d69e 100644 --- a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/CustomerMenuTest.java +++ b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/CustomerMenuTest.java @@ -3,8 +3,13 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import java.util.stream.Stream; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class CustomerMenuTest { @@ -21,37 +26,27 @@ void testFindMenuSuccessful_ReturnIncorrectMenu() { assertThat(menu, is(CustomerMenu.INCORRECT_MENU)); } - @Test + @ParameterizedTest(name = "{0}번을 받으면 {1}을 반환한다.") + @MethodSource("stringAndCustomerMenuProvider") @DisplayName("입력 받은 메뉴 번호에 맞는 고객 메뉴를 반환한다.") - void testFindMenuSuccessful() { - //given - String createMenu = "1"; - String listMenu = "2"; - String searchMenu = "3"; - String updateMenu = "4"; - String deleteMenu = "5"; - String blacklistMEnu = "6"; - String searchVoucherMenu = "7"; - String removeVoucherMenu = "8"; - + void testFindMenuSuccessful(String input, CustomerMenu expectedMenu) { //when - CustomerMenu create = CustomerMenu.findCustomerMenu(createMenu); - CustomerMenu list = CustomerMenu.findCustomerMenu(listMenu); - CustomerMenu search = CustomerMenu.findCustomerMenu(searchMenu); - CustomerMenu update = CustomerMenu.findCustomerMenu(updateMenu); - CustomerMenu delete = CustomerMenu.findCustomerMenu(deleteMenu); - CustomerMenu blacklist = CustomerMenu.findCustomerMenu(blacklistMEnu); - CustomerMenu searchVoucher = CustomerMenu.findCustomerMenu(searchVoucherMenu); - CustomerMenu removeVoucher = CustomerMenu.findCustomerMenu(removeVoucherMenu); + CustomerMenu customerMenu = CustomerMenu.findCustomerMenu(input); //then - assertThat(create, is(CustomerMenu.CREATE)); - assertThat(list, is(CustomerMenu.LIST)); - assertThat(search, is(CustomerMenu.SEARCH)); - assertThat(update, is(CustomerMenu.UPDATE)); - assertThat(delete, is(CustomerMenu.DELETE)); - assertThat(blacklist, is(CustomerMenu.BLACKLIST)); - assertThat(searchVoucher, is(CustomerMenu.SEARCH_VOUCHERS)); - assertThat(removeVoucher, is(CustomerMenu.REMOVE_VOUCHER)); + assertThat(customerMenu, is(expectedMenu)); + } + + static Stream stringAndCustomerMenuProvider() { + return Stream.of( + Arguments.of("1", CustomerMenu.CREATE), + Arguments.of("2", CustomerMenu.LIST), + Arguments.of("3", CustomerMenu.SEARCH), + Arguments.of("4", CustomerMenu.UPDATE), + Arguments.of("5", CustomerMenu.DELETE), + Arguments.of("6", CustomerMenu.BLACKLIST), + Arguments.of("7", CustomerMenu.SEARCH_VOUCHERS), + Arguments.of("8", CustomerMenu.REMOVE_VOUCHER) + ); } } diff --git a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuHandlerTest.java b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuHandlerTest.java index fa991e1c2d..a536c74de5 100644 --- a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuHandlerTest.java +++ b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuHandlerTest.java @@ -13,6 +13,7 @@ import org.springframework.test.context.ActiveProfiles; import com.programmers.vouchermanagement.consoleapp.io.ConsoleManager; +import com.programmers.vouchermanagement.customer.repository.CustomerRepository; import com.programmers.vouchermanagement.voucher.repository.VoucherRepository; @SpringBootTest @@ -21,6 +22,8 @@ class MenuHandlerTest { @Autowired VoucherRepository voucherRepository; @Autowired + CustomerRepository customerRepository; + @Autowired MockTextTerminal textTerminal; @Autowired ConsoleManager consoleManager; @@ -108,17 +111,16 @@ void testExecuteCustomerMenu_IncorrectMenu() { void testExecuteCustomerMenu_CorrectMenu() { //given textTerminal.getInputs() - .add("6"); + .addAll(List.of("1", "tester")); //when menuHandler.handleMenu(Menu.CUSTOMER); String output = textTerminal.getOutput(); //then - assertThat(output, containsString("Customer ID :")); - assertThat(output, containsString("Customer Name :")); + assertThat(output, containsString("successfully saved")); //clean - voucherRepository.deleteAll(); + customerRepository.deleteAll(); } } diff --git a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuTest.java b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuTest.java index d55322dad0..72a0b37343 100644 --- a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuTest.java +++ b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/MenuTest.java @@ -3,8 +3,13 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import java.util.stream.Stream; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class MenuTest { @@ -21,19 +26,22 @@ void testFindMenuSuccessful_ReturnIncorrectMenu() { assertThat(menu.isIncorrect(), is(true)); } - @Test + @ParameterizedTest(name = "{0}번을 받으면 {1}을 반환한다.") + @MethodSource("stringAndMenuProvider") @DisplayName("입력 받은 메뉴 번호에 맞는 메뉴를 반환한다.") - void testFindMenuSuccessful() { - //given - String menuInput = "1"; - String exit = "0"; - + void testFindMenuSuccessful(String input, Menu expectedMenu) { //when - Menu menu = Menu.findMenu(menuInput); - Menu exitMenu = Menu.findMenu(exit); + Menu menu = Menu.findMenu(input); //then - assertThat(menu, is(Menu.VOUCHER)); - assertThat(exitMenu.isExit(), is(true)); + assertThat(menu, is(expectedMenu)); + } + + static Stream stringAndMenuProvider() { + return Stream.of( + Arguments.of("0", Menu.EXIT), + Arguments.of("1", Menu.VOUCHER), + Arguments.of("2", Menu.CUSTOMER) + ); } } diff --git a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/VoucherMenuTest.java b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/VoucherMenuTest.java index 538c96472d..63939bb46e 100644 --- a/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/VoucherMenuTest.java +++ b/src/test/java/com/programmers/vouchermanagement/consoleapp/menu/VoucherMenuTest.java @@ -3,8 +3,13 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import java.util.stream.Stream; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; class VoucherMenuTest { @@ -21,34 +26,26 @@ void testFindMenuSuccessful_ReturnIncorrectMenu() { assertThat(menu, is(VoucherMenu.INCORRECT_MENU)); } - @Test + @ParameterizedTest(name = "{0}을 받으면 {1}을 출력한다.") + @MethodSource("stringAndVoucherMenuProvider") @DisplayName("입력 받은 메뉴 번호에 맞는 바우처 메뉴를 반환한다.") - void testFindMenuSuccessful() { - //given - String createMenu = "1"; - String listMenu = "2"; - String searchMenu = "3"; - String updateMenu = "4"; - String deleteMenu = "5"; - String grantMenu = "6"; - String searchOwnerMenu = "7"; - + void testFindMenuSuccessful(String input, VoucherMenu expectedMenu) { //when - VoucherMenu create = VoucherMenu.findVoucherMenu(createMenu); - VoucherMenu list = VoucherMenu.findVoucherMenu(listMenu); - VoucherMenu search = VoucherMenu.findVoucherMenu(searchMenu); - VoucherMenu update = VoucherMenu.findVoucherMenu(updateMenu); - VoucherMenu delete = VoucherMenu.findVoucherMenu(deleteMenu); - VoucherMenu grant = VoucherMenu.findVoucherMenu(grantMenu); - VoucherMenu searchOwner = VoucherMenu.findVoucherMenu(searchOwnerMenu); + VoucherMenu menu = VoucherMenu.findVoucherMenu(input); //then - assertThat(create, is(VoucherMenu.CREATE)); - assertThat(list, is(VoucherMenu.LIST)); - assertThat(search, is(VoucherMenu.SEARCH)); - assertThat(update, is(VoucherMenu.UPDATE)); - assertThat(delete, is(VoucherMenu.DELETE)); - assertThat(grant, is(VoucherMenu.GRANT)); - assertThat(searchOwner, is(VoucherMenu.SEARCH_OWNER)); + assertThat(menu, is(expectedMenu)); + } + + static Stream stringAndVoucherMenuProvider() { + return Stream.of( + Arguments.of("1", VoucherMenu.CREATE), + Arguments.of("2", VoucherMenu.LIST), + Arguments.of("3", VoucherMenu.SEARCH), + Arguments.of("4", VoucherMenu.UPDATE), + Arguments.of("5", VoucherMenu.DELETE), + Arguments.of("6", VoucherMenu.GRANT), + Arguments.of("7", VoucherMenu.SEARCH_OWNER) + ); } } diff --git a/src/test/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepositoryTest.java index 857cc57003..ea2ece59b3 100644 --- a/src/test/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepositoryTest.java +++ b/src/test/java/com/programmers/vouchermanagement/customer/repository/JdbcCustomerRepositoryTest.java @@ -17,24 +17,18 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.test.context.ActiveProfiles; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; -import com.programmers.vouchermanagement.configuration.properties.file.FileProperties; import com.programmers.vouchermanagement.customer.domain.Customer; import com.zaxxer.hikari.HikariDataSource; -@SpringBootTest @Testcontainers(disabledWithoutDocker = true) @TestInstance(TestInstance.Lifecycle.PER_CLASS) -@ActiveProfiles("test") class JdbcCustomerRepositoryTest { @Container private static final MySQLContainer mySQLContainer = new MySQLContainer<>(DockerImageName.parse("mysql:latest")) @@ -43,8 +37,6 @@ class JdbcCustomerRepositoryTest { DataSource dataSource; NamedParameterJdbcTemplate namedParameterJdbcTemplate; CustomerRepository customerRepository; - @Autowired - FileProperties fileProperties; @BeforeAll void setUp() { @@ -54,9 +46,8 @@ void setUp() { .password(mySQLContainer.getPassword()) .type(HikariDataSource.class) .build(); - namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); - customerRepository = new JdbcCustomerRepository(namedParameterJdbcTemplate, fileProperties); + customerRepository = new JdbcCustomerRepository(namedParameterJdbcTemplate); } @Test @@ -72,12 +63,6 @@ void testJdbcCustomerRepositoryCreation() { assertThat(customerRepository, notNullValue()); } - @Test - @DisplayName("저장된 블랙리스트 csv파일을 성공적으로 읽는다.") - void testLoadingBlacklistFileOnInit() { - assertThat(fileProperties.getCSVCustomerFilePath(), is("src/test/resources/blacklist-test.csv")); - } - @Test @DisplayName("블랙리스트에 저장된 고객이 없을 시 빈 리스트를 반환한다.") void testFindBlackCustomersSuccessful_ReturnEmptyList() { diff --git a/src/test/java/com/programmers/vouchermanagement/util/FormatterTest.java b/src/test/java/com/programmers/vouchermanagement/util/FormatterTest.java index a2da8ebb84..c81f9e4cb0 100644 --- a/src/test/java/com/programmers/vouchermanagement/util/FormatterTest.java +++ b/src/test/java/com/programmers/vouchermanagement/util/FormatterTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import com.programmers.vouchermanagement.customer.domain.Customer; import com.programmers.vouchermanagement.customer.dto.CustomerResponse; @@ -16,43 +18,15 @@ import com.programmers.vouchermanagement.voucher.dto.VoucherResponse; class FormatterTest { - @Test - @DisplayName("바우처가 없을 때의 포매터 문구를 출력한다.") - void testFormatNoContent_Voucher() { - //given - String contentType = "voucher"; - - //when - String formatted = Formatter.formatNoContent(contentType); - - //then - assertThat(formatted, containsString("There is no voucher stored yet!")); - } - - @Test - @DisplayName("고객이 없을 때의 포매터 문구를 출력한다.") - void testFormatNoContent_Customer() { - //given - String contentType = "customer"; - - //when - String formatted = Formatter.formatNoContent(contentType); - - //then - assertThat(formatted, containsString("There is no customer stored yet!")); - } - - @Test - @DisplayName("블랙리스트가 없을 때의 포매터 문구를 출력한다.") - void testFormatNoContent_BlackCustomer() { - //given - String contentType = "black customer"; - + @ParameterizedTest(name = "전달된 {0}가 없을 때의 포메터 문구를 출력한다.") + @ValueSource(strings = {"voucher", "customer", "black customer"}) + @DisplayName("컨텐츠가 없을 때의 포맷을 테스트한다.") + void testFormatNoContent(String contentType) { //when String formatted = Formatter.formatNoContent(contentType); //then - assertThat(formatted, containsString("There is no black customer stored yet!")); + assertThat(formatted, containsString("There is no " + contentType + " stored yet!")); } @Test diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/controller/VoucherControllerTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/controller/VoucherControllerTest.java index 54d11ebadf..0f7b87ae3c 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/controller/VoucherControllerTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/controller/VoucherControllerTest.java @@ -49,7 +49,7 @@ void setUp() { @DisplayName("바우처 생성을 성공하고 바우처 생성 성공 뷰를 출력한다.") void testCreateVoucher_ViewShowingCreatedVoucher() { //given - CreateVoucherRequest request = new CreateVoucherRequest(new BigDecimal(10000), VoucherType.FIXED); + CreateVoucherRequest request = new CreateVoucherRequest(10000, "Fixed"); //when voucherController.create(request); @@ -58,7 +58,7 @@ void testCreateVoucher_ViewShowingCreatedVoucher() { //then UUID createdCVoucherId = voucherService.readAllVouchers() .stream() - .map(VoucherResponse::getVoucherId) + .map(VoucherResponse::voucherId) .findFirst() .orElse(UUID.randomUUID()); assertThat(output, containsString(createdCVoucherId.toString())); @@ -83,7 +83,7 @@ void testReadAllVouchers_NoVoucher() { @DisplayName("저장된 바우처들의 전체 조회를 성공하고 바우처 정보를 보여주는 뷰를 출력한다.") void testReadAllVouchers_ViewShowingVouchers() { //given - CreateVoucherRequest request = new CreateVoucherRequest(new BigDecimal(10000), VoucherType.FIXED); + CreateVoucherRequest request = new CreateVoucherRequest(10000, "Fixed"); voucherController.create(request); //when @@ -93,7 +93,7 @@ void testReadAllVouchers_ViewShowingVouchers() { //then UUID foundVoucherId = voucherService.readAllVouchers() .stream() - .map(VoucherResponse::getVoucherId) + .map(VoucherResponse::voucherId) .findFirst() .orElse(UUID.randomUUID()); assertThat(output, containsString("Voucher ID : " + foundVoucherId)); @@ -118,9 +118,9 @@ void testFindVoucherById_ViewShowingFoundVoucher() { @DisplayName("바우처 정보 업데이트 성공 시 성공 문구를 담은 뷰를 출력한다.") void testUpdateVoucher_ViewShowingUpdatedVoucher() { //given - CreateVoucherRequest createRequest = new CreateVoucherRequest(new BigDecimal(10000), VoucherType.FIXED); + CreateVoucherRequest createRequest = new CreateVoucherRequest(10000, "Fixed"); VoucherResponse voucher = voucherService.create(createRequest); - UpdateVoucherRequest updateRequest = new UpdateVoucherRequest(voucher.getVoucherId(), new BigDecimal(20000), VoucherType.FIXED); + UpdateVoucherRequest updateRequest = new UpdateVoucherRequest(voucher.voucherId(), 20000, "Fixed"); //when voucherController.update(updateRequest); @@ -135,11 +135,11 @@ void testUpdateVoucher_ViewShowingUpdatedVoucher() { @DisplayName("바우처의 삭제 성공 후 성공 문구를 담은 뷰를 출력한다.") void testDeleteVoucherById_SuccessfullyDeleted() { //given - CreateVoucherRequest createRequest = new CreateVoucherRequest(new BigDecimal(10000), VoucherType.FIXED); + CreateVoucherRequest createRequest = new CreateVoucherRequest(10000, "Fixed"); VoucherResponse voucher = voucherService.create(createRequest); //when - voucherController.deleteById(voucher.getVoucherId()); + voucherController.deleteById(voucher.voucherId()); String output = textTerminal.getOutput(); //then @@ -150,11 +150,11 @@ void testDeleteVoucherById_SuccessfullyDeleted() { @DisplayName("바우처 할당 성공 시 성공 문구를 담은 뷰를 출력한다.") void testGrantVoucherToCustomer_SuccessfullyGranted() { //given - CreateVoucherRequest createRequest = new CreateVoucherRequest(new BigDecimal(10000), VoucherType.FIXED); + CreateVoucherRequest createRequest = new CreateVoucherRequest(10000, "Fixed"); VoucherResponse voucher = voucherService.create(createRequest); Customer customer = new Customer(UUID.randomUUID(), "test-customer"); customerRepository.save(customer); - VoucherCustomerRequest request = new VoucherCustomerRequest(voucher.getVoucherId(), customer.getCustomerId()); + VoucherCustomerRequest request = new VoucherCustomerRequest(voucher.voucherId(), customer.getCustomerId()); //when voucherController.grantToCustomer(request); diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/domain/VoucherTypeTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/domain/VoucherTypeTest.java index 464aeb8eaf..9f5c5a31f8 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/domain/VoucherTypeTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/domain/VoucherTypeTest.java @@ -16,7 +16,7 @@ void testFindVoucherTypeFailed_ReturnEmptyOptional() { String desiredVoucherType = "NoCount"; //when - assertThatThrownBy(() -> VoucherType.findVoucherTypeByName(desiredVoucherType)) + assertThatThrownBy(() -> VoucherType.findVoucherType(desiredVoucherType)) .isInstanceOf(IllegalArgumentException.class); } @@ -28,8 +28,8 @@ void testFindVoucherTypeSuccessful_LowerCase() { String percentType = "percent"; //when - VoucherType fixedVoucherType = VoucherType.findVoucherTypeByName(fixedType); - VoucherType percentVoucherType = VoucherType.findVoucherTypeByName(percentType); + VoucherType fixedVoucherType = VoucherType.findVoucherType(fixedType); + VoucherType percentVoucherType = VoucherType.findVoucherType(percentType); //then assertThat(fixedVoucherType, is(VoucherType.FIXED)); @@ -44,8 +44,8 @@ void testFindVoucherTypeSuccessful_CaseInsensitive() { String percentType = "PERCENT"; //when - VoucherType fixedVoucherType = VoucherType.findVoucherTypeByName(fixedType); - VoucherType percentVoucherType = VoucherType.findVoucherTypeByName(percentType); + VoucherType fixedVoucherType = VoucherType.findVoucherType(fixedType); + VoucherType percentVoucherType = VoucherType.findVoucherType(percentType); //then assertThat(fixedVoucherType, is(VoucherType.FIXED)); diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepositoryTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepositoryTest.java index be40ab46ce..8d4e92339a 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepositoryTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/repository/JdbcVoucherRepositoryTest.java @@ -114,7 +114,7 @@ void testFindVoucherByIdSuccessful_ReturnList() { //then assertThat(foundVoucher.isEmpty(), is(false)); - assertThat(foundVoucher.get(), samePropertyValuesAs(voucher)); + assertThat(foundVoucher.get().getVoucherId(), is(voucher.getVoucherId())); } @Test @@ -145,7 +145,7 @@ void testVoucherUpdateSuccessful() { Voucher newlyFoundVoucher = voucherRepository.findById(foundVoucher.getVoucherId()).get(); //then - assertThat(newlyFoundVoucher, samePropertyValuesAs(updatedVoucher)); + assertThat(newlyFoundVoucher.getDiscountValue(), is(updatedVoucher.getDiscountValue())); } @Test diff --git a/src/test/java/com/programmers/vouchermanagement/voucher/service/VoucherServiceTest.java b/src/test/java/com/programmers/vouchermanagement/voucher/service/VoucherServiceTest.java index 8f0ee0c89c..eb281d3ad5 100644 --- a/src/test/java/com/programmers/vouchermanagement/voucher/service/VoucherServiceTest.java +++ b/src/test/java/com/programmers/vouchermanagement/voucher/service/VoucherServiceTest.java @@ -53,13 +53,13 @@ void setUp() { @DisplayName("고정 금액 바우처 생성에 성공한다.") void testFixedVoucherCreationSuccessful() { //given - CreateVoucherRequest request = new CreateVoucherRequest(new BigDecimal("100"), VoucherType.FIXED); + CreateVoucherRequest request = new CreateVoucherRequest(10000, "Fixed"); //when VoucherResponse voucher = voucherService.create(request); //then - Voucher createdVoucher = voucherRepository.findById(voucher.getVoucherId()) + Voucher createdVoucher = voucherRepository.findById(voucher.voucherId()) .get(); assertThat(VoucherResponse.from(createdVoucher), samePropertyValuesAs(voucher)); } @@ -68,7 +68,7 @@ void testFixedVoucherCreationSuccessful() { @DisplayName("유효하지 않은 할인 값의 고정 금액 바우처 생성에 실패한다.") void testFixedVoucherCreationFailed_InvalidAmount() { //given - CreateVoucherRequest request = new CreateVoucherRequest(new BigDecimal("0"), VoucherType.FIXED); + CreateVoucherRequest request = new CreateVoucherRequest(0, "1"); //when assertThatThrownBy(() -> voucherService.create(request)) @@ -84,13 +84,13 @@ void testFixedVoucherCreationFailed_InvalidAmount() { @DisplayName("퍼센트 할인 바우처 생성에 성공한다.") void textPercentVoucherCreationSuccessful() { //given - CreateVoucherRequest request = new CreateVoucherRequest(new BigDecimal("50"), VoucherType.PERCENT); + CreateVoucherRequest request = new CreateVoucherRequest(50, "Percent"); //when VoucherResponse voucher = voucherService.create(request); //then - Voucher createdVoucher = voucherRepository.findById(voucher.getVoucherId()) + Voucher createdVoucher = voucherRepository.findById(voucher.voucherId()) .get(); assertThat(VoucherResponse.from(createdVoucher), samePropertyValuesAs(voucher)); } @@ -99,8 +99,8 @@ void textPercentVoucherCreationSuccessful() { @DisplayName("유효하지 않은 할인율의 퍼센트 할인 바우처 생성에 실패한다.") void testPercentVoucherCreationFailed_InvalidPercent() { //given - CreateVoucherRequest firstRequest = new CreateVoucherRequest(new BigDecimal("0"), VoucherType.PERCENT); - CreateVoucherRequest secondRequest = new CreateVoucherRequest(new BigDecimal("100.1"), VoucherType.PERCENT); + CreateVoucherRequest firstRequest = new CreateVoucherRequest(0, "Percent"); + CreateVoucherRequest secondRequest = new CreateVoucherRequest(101, "Percent"); //when assertThatThrownBy(() -> voucherService.create(firstRequest)) @@ -171,7 +171,7 @@ void testFindVoucherByIdSuccessful() { @DisplayName("존재하지 않는 바우처의 정보 수정을 실패한다.") void testUpdateVoucherFailed_NonExistentVoucher() { //given - UpdateVoucherRequest request = new UpdateVoucherRequest(UUID.randomUUID(), new BigDecimal(1000), VoucherType.FIXED); + UpdateVoucherRequest request = new UpdateVoucherRequest(UUID.randomUUID(), 1000, "Fixed"); //when assertThatThrownBy(() -> voucherService.update(request)) @@ -189,7 +189,7 @@ void testUpdateVoucherFailed_InvalidDiscountValue() { //given Voucher voucher = new Voucher(UUID.randomUUID(), new BigDecimal(10000), VoucherType.FIXED); voucherRepository.save(voucher); - UpdateVoucherRequest request = new UpdateVoucherRequest(voucher.getVoucherId(), BigDecimal.ZERO, VoucherType.FIXED); + UpdateVoucherRequest request = new UpdateVoucherRequest(voucher.getVoucherId(), 0, "Fixed"); //when assertThatThrownBy(() -> voucherService.update(request)) @@ -208,7 +208,7 @@ void testUpdateVoucherSuccessful() { //given Voucher voucher = new Voucher(UUID.randomUUID(), new BigDecimal(10000), VoucherType.FIXED); voucherRepository.save(voucher); - UpdateVoucherRequest request = new UpdateVoucherRequest(voucher.getVoucherId(), BigDecimal.TEN, VoucherType.PERCENT); + UpdateVoucherRequest request = new UpdateVoucherRequest(voucher.getVoucherId(), 10, "Percent"); //when VoucherResponse voucherResponse = voucherService.update(request); diff --git a/src/test/resources/init.sql b/src/test/resources/init.sql index 4c77b60154..3d08a794ca 100644 --- a/src/test/resources/init.sql +++ b/src/test/resources/init.sql @@ -6,6 +6,7 @@ CREATE TABLE customers ( CREATE TABLE vouchers ( voucher_id BINARY(16) PRIMARY KEY, + created_at DATETIME NOT NULL, discount_value DECIMAL NOT NULL, voucher_type VARCHAR(10) NOT NULL, customer_id BINARY(16), diff --git a/src/test/resources/voucher-test.json b/src/test/resources/voucher-test.json index d5860173e7..e69de29bb2 100644 --- a/src/test/resources/voucher-test.json +++ b/src/test/resources/voucher-test.json @@ -1,26 +0,0 @@ -[ { - "voucher_type" : "PERCENT", - "voucher_id" : "11dd24b8-95af-4634-a8c7-b12cfe2c389a", - "discount_value" : "50", - "customer_id" : null -}, { - "voucher_type" : "PERCENT", - "voucher_id" : "56096331-5d50-4c62-9951-80c905e61ba1", - "discount_value" : "50", - "customer_id" : null -}, { - "voucher_type" : "FIXED", - "voucher_id" : "7e5ca090-27fe-4e62-844e-0a64557bcb7a", - "discount_value" : "10000", - "customer_id" : null -}, { - "voucher_type" : "FIXED", - "voucher_id" : "f470205b-c64a-448f-ad83-46497ceb8833", - "discount_value" : "5000", - "customer_id" : null -}, { - "voucher_type" : "PERCENT", - "voucher_id" : "bae97b45-a4f8-4a55-8eee-c455f9a63322", - "discount_value" : "40", - "customer_id" : null -} ] \ No newline at end of file