diff --git a/build.gradle b/build.gradle index f3158f0b0f..41f5968d2a 100644 --- a/build.gradle +++ b/build.gradle @@ -23,8 +23,16 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework:spring-oxm:6.0.13' + implementation 'com.thoughtworks.xstream:xstream:1.4.20' + runtimeOnly 'com.mysql:mysql-connector-j' + testCompileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/java/org/prgms/springbootbasic/BasicApplication.java b/src/main/java/org/prgms/springbootbasic/BasicApplication.java index e4a8fdb21f..8145efec01 100644 --- a/src/main/java/org/prgms/springbootbasic/BasicApplication.java +++ b/src/main/java/org/prgms/springbootbasic/BasicApplication.java @@ -1,17 +1,24 @@ package org.prgms.springbootbasic; -import org.prgms.springbootbasic.controller.MainController; +import org.prgms.springbootbasic.controller.ConsoleController; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; +import java.util.Arrays; + @SpringBootApplication public class BasicApplication { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(BasicApplication.class, args); - var mainController = applicationContext.getBean(MainController.class); + String[] activeProfiles = applicationContext.getEnvironment().getActiveProfiles(); + + if (Arrays.stream(activeProfiles) + .anyMatch(profile -> profile.equals("dev") || profile.equals("local") || profile.equals("test"))) { // 리스트로 + var mainController = applicationContext.getBean(ConsoleController.class); - mainController.run(); + mainController.run(); + } } } diff --git a/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java b/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java index 4b2592fc94..3d8b1205d8 100644 --- a/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java +++ b/src/main/java/org/prgms/springbootbasic/common/CommonConstant.java @@ -1,5 +1,11 @@ package org.prgms.springbootbasic.common; +import java.time.LocalDateTime; + public class CommonConstant { public static final String CSV_PATTERN = ",(?=([^\"]*\"[^\"]*\")*[^\"]*$)"; + public static final int INPUT_FIXED_AMOUNT_VOUCHER = 1; + public static final int INPUT_PERCENT_DISCOUNT_VOUCHER = 2; + public static final LocalDateTime MIN_LOCAL_DATE_TIME = LocalDateTime.of(1000, 1, 1, 1, 1, 1); + public static final LocalDateTime MAX_LOCAL_DATE_TIME = LocalDateTime.of(2060, 1, 1, 1, 1, 1); } diff --git a/src/main/java/org/prgms/springbootbasic/common/Console.java b/src/main/java/org/prgms/springbootbasic/common/Console.java deleted file mode 100644 index 8ba68b94fd..0000000000 --- a/src/main/java/org/prgms/springbootbasic/common/Console.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.prgms.springbootbasic.common; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.domain.VoucherType; -import org.springframework.stereotype.Component; - -import java.util.*; - -@Component -@Slf4j -public class Console { // view - domain 분리 필. Controller를 따로 만들고, 거기서 View, Service 호출. - private static final Scanner CONSOLE_INPUT = new Scanner(System.in); - - public static String readCommand() { - System.out.println("=== Voucher Program ==="); - System.out.println("Type 'exit' to exit the program."); - System.out.println("Type 'create' to create a new voucher."); - System.out.println("Type 'list' to list all vouchers."); - System.out.println("Type 'black' to list customers blacked."); - - return CONSOLE_INPUT.next(); - } - - public static int selectCreateType() { - System.out.println(); - System.out.println("Which voucher would you like to create? Just type number."); - System.out.println("1. FixedAmountVoucher"); - System.out.println("2. PercentDiscountVoucher"); - - return CONSOLE_INPUT.nextInt(); - } - - public static int putDiscountDegree(VoucherType type) { - switch (type){ - case FIXED_AMOUNT -> System.out.println("Enter the fixed discount amount."); - case PERCENT_DISCOUNT -> System.out.println("Enter the discount percentage."); - } - return CONSOLE_INPUT.nextInt(); - } - - - public static String ignoreLine() { - return CONSOLE_INPUT.nextLine(); - } - - public static void printList(Collection collection) { - System.out.println(); - collection.forEach(System.out::println); - System.out.println(); - } - - public static void printArgException() { - System.out.println("Invalid argument. Type command again."); - } - - public static void printRuntimeException() { - System.out.println("Invalid input or system error. redirect to beginning."); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/common/UtilMethod.java b/src/main/java/org/prgms/springbootbasic/common/UtilMethod.java new file mode 100644 index 0000000000..9dc30f47f7 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/common/UtilMethod.java @@ -0,0 +1,13 @@ +package org.prgms.springbootbasic.common; + +import java.nio.ByteBuffer; +import java.util.UUID; + +public class UtilMethod { + public static UUID bytesToUUID(byte[] bytes) { + ByteBuffer wrappedByte = ByteBuffer.wrap(bytes); + + return new UUID(wrappedByte.getLong(), wrappedByte.getLong()); + } + +} diff --git a/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java b/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java index fb2fe2b1ec..a715da97df 100644 --- a/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java +++ b/src/main/java/org/prgms/springbootbasic/common/file/CsvFileTemplate.java @@ -2,6 +2,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import java.io.*; @@ -11,6 +12,7 @@ import java.util.function.Function; @Component +@Profile("test") public class CsvFileTemplate { private static final Logger log = LoggerFactory.getLogger(CsvFileTemplate.class); diff --git a/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java b/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java index 7af4e6de13..6d5ba4ab48 100644 --- a/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java +++ b/src/main/java/org/prgms/springbootbasic/common/file/CustomerCsvFileManager.java @@ -13,7 +13,7 @@ import static org.prgms.springbootbasic.common.CommonConstant.CSV_PATTERN; @Component -@Profile({"dev", "prod"}) +@Profile({"test"}) @Slf4j public class CustomerCsvFileManager { private static final String BLACK_PATH = "./src/main/resources/customer_blacklist.csv"; @@ -31,10 +31,10 @@ public CustomerCsvFileManager(CsvFileTemplate csvFileTemplate) { public List readBlack(){ - return csvFileTemplate.read(BLACK_PATH, this::lineToBlack); + return csvFileTemplate.read(BLACK_PATH, this::convertToBlack); } - private Customer lineToBlack(String line){ + private Customer convertToBlack(String line){ log.debug("line = {}", line); List csvFields = diff --git a/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java b/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java index fd57545ca7..eff7509aa8 100644 --- a/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java +++ b/src/main/java/org/prgms/springbootbasic/common/file/VoucherCsvFileManager.java @@ -2,7 +2,9 @@ import lombok.extern.slf4j.Slf4j; import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.voucher.Voucher; import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -14,13 +16,14 @@ @Component @Slf4j -@Profile({"dev", "prod"}) -public class VoucherCsvFileManager { // CsvFileManager 하나로 합쳐서. domain은 최대한 순수하게 유지. 외부 의존성이 들어간다? 이게 도메인에 들어가면 변경이 취약. -> 분리 - private static final String FILE_PATH = "./src/main/resources/voucher.csv"; +@Profile({"test"}) +public class VoucherCsvFileManager { + @Value("${basic.file.path}") + private String FILE_PATH; private static final String CSV_FIRST_LINE = "UUID,Type,DiscountValue"; private static final int UUID_IDX = 0; private static final int TYPE_IDX = 1; - private static final int DISCOUNT_VALUE_IDX = 2; + private static final int DISCOUNT_DEGREE_IDX = 2; private final CsvFileTemplate csvFileTemplate; @@ -28,15 +31,15 @@ public VoucherCsvFileManager(CsvFileTemplate csvFileTemplate) { this.csvFileTemplate = csvFileTemplate; } - public List read(){ - return csvFileTemplate.read(FILE_PATH, this::lineToVoucher); + public List read(){ + return csvFileTemplate.read(FILE_PATH, this::convertToVoucher); } - public void write(List voucherPolicies){ - csvFileTemplate.write(FILE_PATH, voucherPolicies, this::voucherToString, CSV_FIRST_LINE); + public void write(List voucherPolicies){ + csvFileTemplate.write(FILE_PATH, voucherPolicies, this::convertToString, CSV_FIRST_LINE); } - private VoucherPolicy lineToVoucher(String line){ + private Voucher convertToVoucher(String line){ log.debug("line = {}", line); List splitLine = Arrays.stream(line.split(CSV_PATTERN)) @@ -44,32 +47,31 @@ private VoucherPolicy lineToVoucher(String line){ .toList(); VoucherType[] voucherTypes = VoucherType.values(); - for (VoucherType type : voucherTypes) { - String voucherType = type.getDisplayName(); - String curStringType = splitLine.get(TYPE_IDX); + VoucherType thisVoucherType = + Arrays.stream(voucherTypes) + .filter(type -> type.getDisplayName().equals(splitLine.get(TYPE_IDX))) + .findAny() + .orElseThrow(() -> { + log.error("Invalid voucher type."); + return new IllegalArgumentException("Invalid voucher type"); + }); - if (curStringType.equals(voucherType)) { - log.info("This voucher type is {}", voucherType); + VoucherPolicy voucherPolicy = thisVoucherType.create(); + UUID voucherId = UUID.fromString(splitLine.get(UUID_IDX)); + long discountDegree = Long.parseLong(splitLine.get(DISCOUNT_DEGREE_IDX)); - return type.create( - UUID.fromString(splitLine.get(UUID_IDX)), - Long.parseLong(splitLine.get(DISCOUNT_VALUE_IDX)) - ); - } - } - - log.error("Invalid voucher type."); - throw new IllegalArgumentException("Invalid voucher type."); + return new Voucher(voucherId, discountDegree, voucherPolicy); } - private String voucherToString(VoucherPolicy voucherPolicy){ + private String convertToString(Voucher voucher){ // 외부에 도메인을 맞추면 안됨. -> DB 의존적 클래스랑 실제 내부 도메인 분리. + // 이거를 위해서 VoucherPolicy가 getter를 들고있는게 말이 안됨. 얘를 도메인에 맞춰야지 얘때문에 도메인이 망가지면 안된다. StringBuilder sb = new StringBuilder(); - sb.append(voucherPolicy.getVoucherId()); + sb.append(voucher.getVoucherId()); sb.append(","); - sb.append(voucherPolicy.getClass().getSimpleName()); + sb.append(voucher.getVoucherPolicy().getClass().getSimpleName()); sb.append(","); - sb.append(voucherPolicy.getDiscountAmount()); + sb.append(voucher.getDiscountDegree()); sb.append(System.lineSeparator()); return sb.toString(); diff --git a/src/main/java/org/prgms/springbootbasic/config/MvcConfig.java b/src/main/java/org/prgms/springbootbasic/config/MvcConfig.java new file mode 100644 index 0000000000..af1bf29231 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/config/MvcConfig.java @@ -0,0 +1,29 @@ +package org.prgms.springbootbasic.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.xml.MarshallingHttpMessageConverter; +import org.springframework.oxm.xstream.XStreamMarshaller; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@Configuration +public class MvcConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/v1/**") + .allowedOrigins("*"); + } + + @Override + public void extendMessageConverters(List> converters) { + MarshallingHttpMessageConverter messageConverter = new MarshallingHttpMessageConverter(); + XStreamMarshaller marshaller = new XStreamMarshaller(); + messageConverter.setMarshaller(marshaller); + messageConverter.setUnmarshaller(marshaller); + + converters.add(messageConverter); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/console/Console.java b/src/main/java/org/prgms/springbootbasic/console/Console.java new file mode 100644 index 0000000000..dcbd5793d5 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/console/Console.java @@ -0,0 +1,106 @@ +package org.prgms.springbootbasic.console; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.VoucherType; +import org.springframework.stereotype.Component; + +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Scanner; +import java.util.UUID; + +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_FIXED_AMOUNT_VOUCHER; +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_PERCENT_DISCOUNT_VOUCHER; + +@Component +@Slf4j +public class Console { // Console이 common인가? MVC 생각하며 고민하자. View에 해당한다고 보이는데... MVC가 뭔지 대답 못한 것을 두번째 걸렸다. 공부하자... + public static final Scanner consoleInput = new Scanner(System.in); + + public static String readCommand() { + System.out.println("=== Voucher Program ==="); + System.out.println("Type 'createVoucher' to create a new voucher."); + System.out.println("Type 'createCustomer' to create a new customer."); + System.out.println("Type 'listVoucher' to list all vouchers."); + System.out.println("Type 'listCustomer' to list all customers."); + System.out.println("Type 'black' to list customers blacked."); + System.out.println("Type 'wallet' to enter wallet service."); + System.out.println("Type 'exit' to exit the program."); + + return consoleInput.next(); + } + + public static int selectPolicyType() { + System.out.println(); + System.out.println("Which voucher would you like to create? Just type number."); + System.out.println(MessageFormat.format("{0}. with fixed amount discount policy", INPUT_FIXED_AMOUNT_VOUCHER)); + System.out.println(MessageFormat.format("{0}. with percent discount policy", INPUT_PERCENT_DISCOUNT_VOUCHER)); + + return consoleInput.nextInt(); + } + + public static String putCustomerInfo() { + ignoreLine(); + + System.out.println(); + System.out.println("Please enter the name and email of the customer you want to create, separated by a space on a single line."); + + return consoleInput.nextLine(); + } + + public static int putDiscountDegree(VoucherType type) { + switch (type){ + case FIXED_AMOUNT -> System.out.println("Enter the fixed discount amount."); + case PERCENT_DISCOUNT -> System.out.println("Enter the discount percentage."); + } + return consoleInput.nextInt(); + } + + + public static String ignoreLine() { + return consoleInput.nextLine(); + } + + public static void printArgException() { + System.out.println("Invalid argument. Type command again."); + } + + public static void printRuntimeException() { + System.out.println("Invalid input or system error. redirect to beginning."); + } + + public static String readWalletCommand() { + System.out.println(); + System.out.println("Welcome to our wallet service."); + System.out.println("Type 'allocate' if you want to allocate voucher to a customer." ); + System.out.println("Type 'delete' if you want to delete voucher from a customer."); + System.out.println("Type 'showVoucherByCustomer' to view customers with a voucher."); + System.out.println("Type 'showCustomerByVoucher' to view vouchers that a customer has."); + System.out.println("Type 'back' to go back."); + + return consoleInput.next(); + } + + public static UUID typeCustomerId(){ + System.out.println("Type customer Id."); + + return UUID.fromString(consoleInput.next()); + } + + public static UUID typeVoucherId(){ + System.out.println("Type voucher Id."); + + return UUID.fromString(consoleInput.next()); + } + + public static void success(String command){ + System.out.println("'" + command + "' command successfully executed."); + System.out.println(); + } + + public static void printList(Collection collection) { + System.out.println(); + collection.forEach(System.out::println); + System.out.println(); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/ConsoleController.java b/src/main/java/org/prgms/springbootbasic/controller/ConsoleController.java new file mode 100644 index 0000000000..609cb5fb17 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/ConsoleController.java @@ -0,0 +1,194 @@ +package org.prgms.springbootbasic.controller; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.service.dto.VoucherInsertDto; +import org.prgms.springbootbasic.service.dto.VoucherResponseDto; +import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.service.CustomerService; +import org.prgms.springbootbasic.service.CustomerVoucherManagementService; +import org.prgms.springbootbasic.service.VoucherService; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Controller; + +import java.util.InputMismatchException; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + +import static org.prgms.springbootbasic.console.Console.*; + +@Controller +@Slf4j +public class ConsoleController { + private static final String EXIT = "exit"; + private static final String LIST_VOUCHER = "listVoucher"; + private static final String LIST_CUSTOMER = "listCustomer"; + private static final String CREATE_VOUCHER = "createVoucher"; + private static final String CREATE_CUSTOMER = "createCustomer"; + private static final String BLACK = "black"; + private static final String WALLET = "wallet"; + private static final String ALLOCATE = "allocate"; + private static final String DELETE = "delete"; + private static final String SHOW_CUSTOMER_BY_VOUCHER = "showVoucherByCustomer"; + private static final String SHOW_VOUCHER_BY_CUSTOMER = "showCustomerByVoucher"; + private static final String BACK = "back"; + + private final VoucherService voucherService; + private final CustomerService customerService; + private final CustomerVoucherManagementService managementService; + + public ConsoleController(VoucherService voucherService, CustomerService customerService, CustomerVoucherManagementService managementService) { + this.voucherService = voucherService; + this.customerService = customerService; + this.managementService = managementService; + } + + public void run() { + String command = ""; + while (!command.equals(EXIT)) { + try { + command = readCommand(); + + executeCommand(command); + success(command); + } catch (IllegalArgumentException e) { + log.error("{}", e.toString()); + + printArgException(); + } catch (InputMismatchException e) { + String invalidVal = ignoreLine(); + + log.warn("User input = {}", invalidVal); + throw new IllegalArgumentException("Not integer."); + } catch (NoSuchElementException e) { + log.error("input is exhausted"); + throw new RuntimeException("Input is exhausted"); + } catch (IllegalStateException e) { + log.error("Scanner is closed"); + throw new RuntimeException("Scanner is closed."); + } catch (DuplicateKeyException e) { + log.error("Key duplicate error.", e); + printDuplicateKeyException(); + } catch (DataAccessException e) { + log.error("Database error.", e); + printRuntimeException(); + } catch (RuntimeException e) { + printRuntimeException(); + } + } + } // 프론트 컨트롤러 패턴에 대해. 공부해보자. 컨트롤러 일관성 없음. 왜 wallet만 따로 존재하는? 고민... -> 그냥 통일합시다. + + private void printDuplicateKeyException() { + System.out.println("There is duplicate key in the values."); + } + + private void executeCommand(String command) { + switch (command) { + case CREATE_VOUCHER -> createVoucher(); + case CREATE_CUSTOMER -> createCustomer(); + case LIST_VOUCHER -> listVoucher(); + case LIST_CUSTOMER -> listCustomer(); + case BLACK -> black(); + case WALLET -> runWallet(); + case EXIT -> {} + default -> { + log.warn("invalid command. now command = {}", command); + throw new IllegalArgumentException("Invalid command. Type command again."); + } + } + } + + private void createVoucher() { + int voucherSeq = selectPolicyType(); + + VoucherType voucherType = voucherService.convertToType(voucherSeq); + + int discountDegree = putDiscountDegree(voucherType); + + voucherService.insert(new VoucherInsertDto(voucherType, discountDegree)); + } + + private void createCustomer() { + String[] info = putCustomerInfo().split(" "); + + if (info.length != 2) { + throw new IllegalArgumentException("Customer info is not valid."); + } + + String name = info[0]; + String email = info[1]; + + customerService.insert(name, email); + } + + private void listVoucher() { + List vouchers = voucherService.findAll(); + + printList(vouchers); + } + + private void listCustomer() { + List customers = customerService.findAll(); + + printList(customers); + } + + private void black() { + List blacklist = customerService.findBlackAll(); + + printList(blacklist); + } + + + private void runWallet() { + String command = readWalletCommand(); + + executeWalletCommand(command); + success(command); + } + + private void executeWalletCommand(String command) { + switch (command){ + case ALLOCATE -> allocate(); + case DELETE -> delete(); + case SHOW_CUSTOMER_BY_VOUCHER -> showVoucherByCustomer(); + case SHOW_VOUCHER_BY_CUSTOMER -> showCustomerByVoucher(); + case BACK -> {} + default -> { + log.warn("invalid command. now command = {}", command); + throw new IllegalArgumentException("Invalid command. Type command again."); + } + } + } + + private void allocate() { + UUID customerId = typeCustomerId(); + UUID voucherId = typeVoucherId(); + + managementService.allocate(customerId, voucherId); + } + + private void delete() { + UUID customerId = typeCustomerId(); + UUID voucherId = typeVoucherId(); + + managementService.delete(customerId, voucherId); + } + + private void showVoucherByCustomer() { + UUID customerId = typeCustomerId(); + List vouchers = managementService.searchVouchersFromCustomer(customerId); + + printList(vouchers); + } + + private void showCustomerByVoucher() { + UUID voucherId = typeVoucherId(); + List customers = managementService.searchCustomerFromVoucher(voucherId); + + printList(customers); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/HomeController.java b/src/main/java/org/prgms/springbootbasic/controller/HomeController.java new file mode 100644 index 0000000000..7eba09cf09 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/HomeController.java @@ -0,0 +1,46 @@ +package org.prgms.springbootbasic.controller; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.service.dto.VoucherResponseDto; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.service.CustomerService; +import org.prgms.springbootbasic.service.VoucherService; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.util.List; + +@Controller +@Slf4j +public class HomeController { + private final CustomerService customerService; + private final VoucherService voucherService; + + public HomeController(CustomerService customerService, VoucherService voucherService) { + this.customerService = customerService; + this.voucherService = voucherService; + } + + @GetMapping + public String homePage(Model model){ + List customers = customerService.findAll(); //pagination + List vouchers = voucherService.findAll(); + + model.addAttribute("customers", customers); + model.addAttribute("vouchers", vouchers); + + return "index"; + } + + @GetMapping("/temp") + @ResponseBody + public ResponseEntity statusSetting(){ + + return ResponseEntity.status(302) + .header("Location", "/") + .body("body"); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/MainController.java b/src/main/java/org/prgms/springbootbasic/controller/MainController.java deleted file mode 100644 index 3db9514923..0000000000 --- a/src/main/java/org/prgms/springbootbasic/controller/MainController.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.prgms.springbootbasic.controller; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.common.Console; -import org.prgms.springbootbasic.domain.VoucherType; -import org.prgms.springbootbasic.domain.customer.Customer; -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.prgms.springbootbasic.service.CustomerService; -import org.prgms.springbootbasic.service.VoucherService; -import org.springframework.stereotype.Controller; - -import java.util.InputMismatchException; -import java.util.List; -import java.util.NoSuchElementException; - -@Controller -@Slf4j -public class MainController { - private static final String EXIT = "exit"; - private static final String LIST = "list"; - private static final String CREATE = "create"; - private static final String BLACK = "black"; - - private final VoucherService voucherService; - private final CustomerService customerService; - - public MainController(VoucherService voucherService, CustomerService customerService) { - this.voucherService = voucherService; - this.customerService = customerService; - } - - public void run() { - String command = ""; - while (!command.equals(EXIT)) { - try { - command = Console.readCommand(); - - executeCommand(command); - } catch (InputMismatchException e) { - String invalidVal = Console.ignoreLine(); - - log.warn("User input = {}", invalidVal); - throw new IllegalArgumentException("Not integer."); - } catch (NoSuchElementException e) { - log.error("input is exhausted"); - throw new RuntimeException("Input is exhausted"); - } catch (IllegalStateException e) { - log.error("Scanner is closed"); - throw new RuntimeException("Scanner is closed."); - } catch (IllegalArgumentException e) { - Console.printArgException(); - } catch (RuntimeException e) { - Console.printRuntimeException(); - } - } - } - - public void create(){ - int voucherSeq = Console.selectCreateType(); - - VoucherType voucherType = voucherService.seqToType(voucherSeq); - - int discountDegree = Console.putDiscountDegree(voucherType); - - voucherService.create(voucherType, discountDegree); - } - - private void list(){ - List voucherPolicies = voucherService.findAll(); // 뷰가 다른 클래스에 의존 - - Console.printList(voucherPolicies); - } - - private void black(){ - List blacklist = customerService.findBlackAll(); // customerRepository에 의존 - - Console.printList(blacklist); - } - - private void executeCommand(String command) { - switch (command) { - case CREATE -> create(); - case LIST -> list(); - case BLACK -> black(); - case EXIT -> {} - default -> { - log.warn("invalid command. now command = {}", command); - throw new IllegalArgumentException("Invalid command. Type command again."); - } - } - } -} diff --git a/src/main/java/org/prgms/springbootbasic/controller/customer/CustomerApiController.java b/src/main/java/org/prgms/springbootbasic/controller/customer/CustomerApiController.java new file mode 100644 index 0000000000..897f9508b4 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/customer/CustomerApiController.java @@ -0,0 +1,59 @@ +package org.prgms.springbootbasic.controller.customer; + +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.controller.customer.dto.CustomerRequestDto; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.prgms.springbootbasic.service.CustomerService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.prgms.springbootbasic.common.CommonConstant.MAX_LOCAL_DATE_TIME; +import static org.prgms.springbootbasic.common.CommonConstant.MIN_LOCAL_DATE_TIME; + +@RestController +@RequestMapping("/api/v1/customers") +public class CustomerApiController { + private final CustomerService customerService; + + public CustomerApiController(CustomerService customerService) { + this.customerService = customerService; + } + + @GetMapping + public ResponseEntity> showCustomerBetweenDate(@RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { + LocalDateTime startOfDay = (startDate != null) ? + LocalDate.parse(startDate).atStartOfDay() : MIN_LOCAL_DATE_TIME; + LocalDateTime endOfDay = (endDate != null) ? + LocalDate.parse(endDate).atTime(23, 59, 59) : MAX_LOCAL_DATE_TIME; + + return ResponseEntity.ok(customerService.findBetweenLocalDateTime(startOfDay, endOfDay)); + } + + @GetMapping("/{customerId}") + public ResponseEntity showDetails(@PathVariable String customerId) { + Customer customer = customerService.findById(UUID.fromString(customerId)) + .orElseThrow(EntityNotFoundException::new); + + return ResponseEntity.ok(customer); + } + + @PostMapping + public ResponseEntity createCustomer(@RequestBody CustomerRequestDto requestDto) { + return ResponseEntity.ok(customerService.insert(requestDto.name(), requestDto.email())); + } + + @DeleteMapping("/{customerId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCustomer(@PathVariable String customerId) { + UUID customerUUID = UUID.fromString(customerId); + + customerService.deleteById(customerUUID); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/customer/CustomerController.java b/src/main/java/org/prgms/springbootbasic/controller/customer/CustomerController.java new file mode 100644 index 0000000000..d25ac06bcb --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/customer/CustomerController.java @@ -0,0 +1,68 @@ +package org.prgms.springbootbasic.controller.customer; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.controller.customer.dto.CustomerRequestDto; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.prgms.springbootbasic.service.CustomerService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.Objects; +import java.util.UUID; + +@Controller +@RequestMapping("/customers") +@Slf4j +public class CustomerController { + private final CustomerService customerService; + + public CustomerController(CustomerService customerService) { + this.customerService = customerService; + } + + @GetMapping("/create") + public String showCreatePage() { + return "customer/customer-create"; + } + + @PostMapping("/create") + public String create(CustomerRequestDto customerRequestDto) { + log.info(String.valueOf(customerRequestDto)); + + customerService.insert(customerRequestDto.name(), customerRequestDto.email()); + + return "redirect:/"; + } + + @GetMapping("/{customerId}") + public String showDetail(@PathVariable("customerId") String customerId, Model model) { + UUID customerUUID = UUID.fromString(customerId); + + Customer customer = customerService.findById(customerUUID) + .orElseThrow(EntityNotFoundException::new); + model.addAttribute("customer", customer); + + return "customer/customer-details"; + } + + @PostMapping("/{customerId}") + public String update(@PathVariable String customerId, CustomerRequestDto requestDto) { + UUID customerUUID = UUID.fromString(customerId); + String name = requestDto.name(); + String isBlackedStr = requestDto.isBlacked(); + boolean isBlacked = Objects.requireNonNull(isBlackedStr).equals("true"); + + log.info("customerId = {}, name = {}, isBlacked = {}", customerId, name, isBlacked); + + customerService.update(customerUUID, name, isBlacked); + + return "redirect:/"; + } + + @DeleteMapping("/{customerId}") + public void delete(@PathVariable String customerId) { + customerService.deleteById(UUID.fromString(customerId)); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/customer/dto/CustomerRequestDto.java b/src/main/java/org/prgms/springbootbasic/controller/customer/dto/CustomerRequestDto.java new file mode 100644 index 0000000000..5be93398b1 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/customer/dto/CustomerRequestDto.java @@ -0,0 +1,11 @@ +package org.prgms.springbootbasic.controller.customer.dto; + +public record CustomerRequestDto ( + String customerId, + String name, + String email, + String createdAt, + String lastLoginAt, + String isBlacked +) { +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/voucher/VoucherApiController.java b/src/main/java/org/prgms/springbootbasic/controller/voucher/VoucherApiController.java new file mode 100644 index 0000000000..ac91393cbd --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/voucher/VoucherApiController.java @@ -0,0 +1,71 @@ +package org.prgms.springbootbasic.controller.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.service.dto.VoucherCreateRequestDto; +import org.prgms.springbootbasic.service.dto.VoucherInsertDto; +import org.prgms.springbootbasic.service.dto.VoucherResponseDto; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.prgms.springbootbasic.service.VoucherService; +import org.prgms.springbootbasic.service.dto.VoucherFilterDto; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/v1/vouchers") +@Slf4j +public class VoucherApiController { + private final VoucherService voucherService; + + public VoucherApiController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping + public ResponseEntity> showVouchersFiltered(@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDay, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDay, + @RequestParam String voucherPolicy) { + VoucherFilterDto voucherFilterDto = new VoucherFilterDto(startDay, endDay, voucherService.convertToType(voucherPolicy)); + + log.info(voucherFilterDto.toString()); + + // 컨트롤러에서 도메인을 바로 바라보는 것은 도메인 로직까지 사용이 가능해서 컨트롤러 역할이 비대해짐. + // 서비스와 컨트롤러 사이에도 DTO를 둬서 서비스에서만 도메인 처리를 하는 것이 좋음. -> VoucherFilterDto + // 컨트롤러가 도메인 Voucher를 알고 있다면 Voucher 관련 메서드를 사용할 수 있다는 것이니까. -> Controller와 Service 사이에도 도입한다. + // 컨트롤러에서 도메인을 볼 수 없도록 한다. 도메인의 수정은 치명적일 수 있고 여기저기서 수정될 수 있다면 유지보수가 힘들 수 있어 보인다. + // VoucherType을 보는 건 괜찮을까? + List filteredResponseDto = voucherService.findByPolicyBetweenLocalDateTime(voucherFilterDto); + + return ResponseEntity.ok(filteredResponseDto); + } + + @GetMapping("/{voucherId}") + public ResponseEntity showDetails(@PathVariable String voucherId) { + VoucherResponseDto voucherResponseDto = voucherService.findById(UUID.fromString(voucherId)) + .orElseThrow(EntityNotFoundException::new); + + return ResponseEntity.ok(voucherResponseDto); + } + + @PostMapping + public ResponseEntity createVoucher(@RequestBody VoucherCreateRequestDto requestDto) { + VoucherInsertDto voucherInsertDto = new VoucherInsertDto(requestDto.voucherPolicy(), requestDto.discountDegree()); + + VoucherResponseDto voucherResponseDto = voucherService.insert(voucherInsertDto); + + return ResponseEntity.ok(voucherResponseDto); + } + + @DeleteMapping("/{voucherId}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteCustomer(@PathVariable String voucherId) { + UUID voucherUUID = UUID.fromString(voucherId); + + voucherService.deleteById(voucherUUID); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/controller/voucher/VoucherController.java b/src/main/java/org/prgms/springbootbasic/controller/voucher/VoucherController.java new file mode 100644 index 0000000000..218567641d --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/controller/voucher/VoucherController.java @@ -0,0 +1,69 @@ +package org.prgms.springbootbasic.controller.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.service.dto.VoucherCreateRequestDto; +import org.prgms.springbootbasic.service.dto.VoucherInsertDto; +import org.prgms.springbootbasic.service.dto.VoucherResponseDto; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.prgms.springbootbasic.service.VoucherService; +import org.prgms.springbootbasic.service.dto.VoucherUpdateDto; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.UUID; + +@Controller +@RequestMapping("/vouchers") +@Slf4j +public class VoucherController { + private final VoucherService voucherService; + + public VoucherController(VoucherService voucherService) { + this.voucherService = voucherService; + } + + @GetMapping("/create") + public String showCreatePage() { + return "voucher/voucher-create"; + } + + @PostMapping("/create") + public String create(VoucherCreateRequestDto voucherCreateRequestDto) { + VoucherInsertDto voucherInsertDto = new VoucherInsertDto(voucherCreateRequestDto.voucherPolicy(), + voucherCreateRequestDto.discountDegree()); + + voucherService.insert(voucherInsertDto); + + return "redirect:/"; + } + + @GetMapping("/{voucherId}") + public String showDetail(@PathVariable("voucherId") String voucherId, Model model) { + UUID voucherUUID = UUID.fromString(voucherId); + + VoucherResponseDto voucherResponseDto = voucherService.findById(voucherUUID) + .orElseThrow(EntityNotFoundException::new); + model.addAttribute("voucher", voucherResponseDto); + + return "voucher/voucher-details"; + } + + @PostMapping("/{voucherId}") + public String update(@PathVariable String voucherId, VoucherCreateRequestDto requestDto) { + UUID voucherUUID = UUID.fromString(voucherId); + + VoucherUpdateDto voucherUpdateDto = new VoucherUpdateDto(voucherUUID, + requestDto.voucherPolicy(), + requestDto.discountDegree()); + + voucherService.update(voucherUpdateDto); + + return "redirect:/"; + } + + @DeleteMapping("/{voucherId}") + public void delete(@PathVariable String voucherId) { // dto 공부. 계층간 전송. dto는 패키지가 어디에 있어야 하는지 + voucherService.deleteById(UUID.fromString(voucherId)); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java b/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java index 5e951eb9e1..a5e37697f7 100644 --- a/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java +++ b/src/main/java/org/prgms/springbootbasic/domain/VoucherType.java @@ -2,26 +2,28 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.domain.voucher.FixedAmountVoucher; -import org.prgms.springbootbasic.domain.voucher.PercentDiscountVoucher; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import java.util.UUID; -import java.util.function.BiFunction; +import java.util.function.Supplier; + +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_FIXED_AMOUNT_VOUCHER; +import static org.prgms.springbootbasic.common.CommonConstant.INPUT_PERCENT_DISCOUNT_VOUCHER; @Slf4j public enum VoucherType { - FIXED_AMOUNT(1, FixedAmountVoucher::new, "FixedAmountVoucher"), - PERCENT_DISCOUNT(2, PercentDiscountVoucher::new, "PercentDiscountVoucher"); + FIXED_AMOUNT(INPUT_FIXED_AMOUNT_VOUCHER, FixedAmountPolicy::new, "FixedAmountPolicy"), + PERCENT_DISCOUNT(INPUT_PERCENT_DISCOUNT_VOUCHER, PercentDiscountPolicy::new, "PercentDiscountPolicy"); private final int seq; - private final BiFunction biFunction; + private final Supplier createVoucherPolicy; @Getter private final String displayName; - VoucherType(int seq, BiFunction biFunction, String displayName) { + VoucherType(int seq, Supplier createVoucherPolicy, String displayName) { this.seq = seq; - this.biFunction = biFunction; + this.createVoucherPolicy = createVoucherPolicy; this.displayName = displayName; } @@ -36,11 +38,18 @@ public static VoucherType getTypeFromSeq(int seq){ throw new IllegalArgumentException("Invalid seq"); } - public VoucherPolicy create(long discountDegree){ - return this.biFunction.apply(UUID.randomUUID(), discountDegree); + public static VoucherType getTypeFromName(String policyName){ + for (VoucherType type : values()){ + if (type.displayName.equals(policyName)) { + return type; + } + } + + log.warn("user input policy = {}", policyName); + throw new IllegalArgumentException("Invalid policy"); } - public VoucherPolicy create(UUID uuid, long discountDegree){ - return this.biFunction.apply(uuid, discountDegree); + public VoucherPolicy create(){ + return this.createVoucherPolicy.get(); } } diff --git a/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java b/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java index cf42b9750e..fbad2149d0 100644 --- a/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java +++ b/src/main/java/org/prgms/springbootbasic/domain/customer/Customer.java @@ -4,15 +4,23 @@ import java.util.UUID; public class Customer { - private UUID id; + private final UUID customerId; private String name; - private String email; - private LocalDateTime createdAt; + private final String email; + private final LocalDateTime createdAt; private LocalDateTime lastLoginAt; private boolean isBlacked; - public Customer(UUID id, String name, String email, LocalDateTime createdAt, LocalDateTime lastLoginAt, boolean isBlacked) { - this.id = id; + public Customer(UUID customerId, String name, String email, LocalDateTime createdAt) { + this.customerId = customerId; + this.name = name; + this.email = email; + this.createdAt = createdAt; + this.isBlacked = false; + } + + public Customer(UUID customerId, String name, String email, LocalDateTime createdAt, LocalDateTime lastLoginAt, boolean isBlacked) { + this.customerId = customerId; this.name = name; this.email = email; this.createdAt = createdAt; @@ -20,10 +28,41 @@ public Customer(UUID id, String name, String email, LocalDateTime createdAt, Loc this.isBlacked = isBlacked; } + public UUID getCustomerId() { + return customerId; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getLastLoginAt() { + return lastLoginAt; + } + + public boolean isBlacked() { + return isBlacked; + } + + public Customer changeInfo(String name, boolean isBlacked){ + this.name = name; + this.isBlacked = isBlacked; + + return this; + } + @Override public String toString() { return "Customer{" + - "id=" + id + + "id=" + customerId + ", name='" + name + '\'' + ", email='" + email + '\'' + ", createdAt=" + createdAt + diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountPolicy.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountPolicy.java new file mode 100644 index 0000000000..b1fe39730c --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountPolicy.java @@ -0,0 +1,18 @@ +package org.prgms.springbootbasic.domain.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.exception.OutOfRangeException; + +import static java.lang.Math.max; + +@Slf4j +public class FixedAmountPolicy implements VoucherPolicy { + @Override + public long discount(long beforeDiscount, long discountDegree) { + if (beforeDiscount < 0) { + throw new OutOfRangeException("beforeDiscount is less than 0."); + } + + return max(0, beforeDiscount - discountDegree); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountVoucher.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountVoucher.java deleted file mode 100644 index 9eea6ea667..0000000000 --- a/src/main/java/org/prgms/springbootbasic/domain/voucher/FixedAmountVoucher.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.prgms.springbootbasic.domain.voucher; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.exception.OutOfRangeException; - -import java.util.UUID; - -import static java.lang.Math.max; - -@Slf4j -public class FixedAmountVoucher implements VoucherPolicy { - private final UUID voucherId; - private final long amount; - - public FixedAmountVoucher(UUID voucherId, long amount) { - this.voucherId = voucherId; - this.amount = amount; - } - - @Override - public UUID getVoucherId() { - return this.voucherId; - } - - @Override - public long getDiscountAmount() { - return this.amount; - } - - @Override - public long discount(long beforeDiscount) { - if (beforeDiscount < 0) { - throw new OutOfRangeException("beforeDiscount is less than 0."); - } - - return max(0, beforeDiscount - this.amount); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountPolicy.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountPolicy.java new file mode 100644 index 0000000000..542d923e8a --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountPolicy.java @@ -0,0 +1,16 @@ +package org.prgms.springbootbasic.domain.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.exception.OutOfRangeException; + +@Slf4j +public class PercentDiscountPolicy implements VoucherPolicy { + @Override + public long discount(long beforeDiscount, long discountDegree) { + if (beforeDiscount < 0) { + throw new OutOfRangeException("beforeDiscount is less than 0."); + } + + return beforeDiscount * discountDegree / 100L; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountVoucher.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountVoucher.java deleted file mode 100644 index 43ea04aa3f..0000000000 --- a/src/main/java/org/prgms/springbootbasic/domain/voucher/PercentDiscountVoucher.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.prgms.springbootbasic.domain.voucher; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.exception.OutOfRangeException; - -import java.util.UUID; - -@Slf4j -public class PercentDiscountVoucher implements VoucherPolicy { - private final UUID voucherId; - private final long percent; - - public PercentDiscountVoucher(UUID voucherId, long percent) { - if (percent > 100 || percent <= 0) { - log.warn("percent value is out of range."); - throw new OutOfRangeException("percent value is out of range."); - } - - this.voucherId = voucherId; - this.percent = percent; - } - - @Override - public UUID getVoucherId() { - return this.voucherId; - } - - @Override - public long getDiscountAmount() { - return this.percent; - } - - @Override - public long discount(long beforeDiscount) { // 음수 고려 - if (beforeDiscount < 0) - throw new OutOfRangeException("beforeDiscount is less than 0."); - return beforeDiscount * percent / 100L; - } -} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/Voucher.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/Voucher.java new file mode 100644 index 0000000000..dd0b2a0cd0 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/Voucher.java @@ -0,0 +1,74 @@ +package org.prgms.springbootbasic.domain.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.exception.OutOfRangeException; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Slf4j +public class Voucher { + private final UUID voucherId; + private final long discountDegree; + + private final VoucherPolicy voucherPolicy; + private final LocalDateTime createdAt; + + public Voucher(UUID voucherId, long discountDegree, VoucherPolicy voucherPolicy, LocalDateTime createdAt) { + validDiscountDegree(discountDegree, voucherPolicy); + + this.voucherId = voucherId; + this.discountDegree = discountDegree; + this.voucherPolicy = voucherPolicy; + this.createdAt = createdAt; + } + + public Voucher(UUID voucherId, long discountDegree, VoucherPolicy voucherPolicy) { + validDiscountDegree(discountDegree, voucherPolicy); + + this.voucherId = voucherId; + this.discountDegree = discountDegree; + this.voucherPolicy = voucherPolicy; + this.createdAt = LocalDateTime.now(); + } + + public UUID getVoucherId() { + return voucherId; + } + + public long getDiscountDegree() { + return discountDegree; + } + + public VoucherPolicy getVoucherPolicy() { + return voucherPolicy; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public long discount(long beforeDiscount){ + return voucherPolicy.discount(beforeDiscount, this.discountDegree); + } + + @Override + public String toString() { + return "Voucher{" + + "voucherId=" + voucherId + + ", discountDegree=" + discountDegree + + ", voucherPolicy=" + voucherPolicy.getClass().getSimpleName() + "@" + voucherPolicy.hashCode() + + '}'; + } + + + + private static void validDiscountDegree(long discountDegree, VoucherPolicy voucherPolicy) { + if (voucherPolicy instanceof PercentDiscountPolicy) { + if (discountDegree <= 0 || discountDegree > 100) { + log.error("percent value is out of range."); + throw new OutOfRangeException("percent value is out of range."); + } + } + } +} diff --git a/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java b/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java index 51dd756124..72e073810d 100644 --- a/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java +++ b/src/main/java/org/prgms/springbootbasic/domain/voucher/VoucherPolicy.java @@ -1,9 +1,5 @@ package org.prgms.springbootbasic.domain.voucher; -import java.util.UUID; - public interface VoucherPolicy { - UUID getVoucherId(); // VoucherPolicy를 file로 쓸 때 id와 discount 정도를 알아야 함. - long discount(long beforeDiscount); - long getDiscountAmount(); // VoucherPolicy를 file로 쓸 때 id와 discount 정도를 알아야 함. + long discount(long beforeDiscount, long discountDegree); } diff --git a/src/main/java/org/prgms/springbootbasic/exception/EntityNotFoundException.java b/src/main/java/org/prgms/springbootbasic/exception/EntityNotFoundException.java new file mode 100644 index 0000000000..046b3083f4 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/exception/EntityNotFoundException.java @@ -0,0 +1,6 @@ +package org.prgms.springbootbasic.exception; + +public class EntityNotFoundException extends RuntimeException{ + public EntityNotFoundException() { + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/CustomerFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/CustomerFileRepository.java deleted file mode 100644 index 7db1b08835..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/CustomerFileRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.common.file.CustomerCsvFileManager; -import org.prgms.springbootbasic.domain.customer.Customer; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -@Profile({"dev", "prod"}) -public class CustomerFileRepository implements CustomerRepository { - private final CustomerCsvFileManager customerCsvFileManager; - - public CustomerFileRepository(CustomerCsvFileManager customerCsvFileManager) { - this.customerCsvFileManager = customerCsvFileManager; - } - - @Override - public List findBlackAll() { - return customerCsvFileManager.readBlack(); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/CustomerMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/CustomerMemoryRepository.java deleted file mode 100644 index 7c6caebd51..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/CustomerMemoryRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.domain.customer.Customer; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import java.util.ArrayList; -import java.util.List; - -@Repository -@Profile({"local", "test"}) -public class CustomerMemoryRepository implements CustomerRepository { - @Override - public List findBlackAll() { - return new ArrayList<>(); - } -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/CustomerRepository.java b/src/main/java/org/prgms/springbootbasic/repository/CustomerRepository.java deleted file mode 100644 index 09a3731d46..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/CustomerRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.domain.customer.Customer; - -import java.util.List; - -public interface CustomerRepository { - List findBlackAll(); -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/VoucherFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/VoucherFileRepository.java deleted file mode 100644 index 6bcbd999d6..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/VoucherFileRepository.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.common.file.VoucherCsvFileManager; -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -@Repository -@Profile({"dev", "prod"}) -@Primary -@Slf4j -public class VoucherFileRepository implements VoucherRepository { // csv를 다루니 이름은 Csv를 붙여 더 명확히 해야 하지 않나 싶다. - private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); - private final VoucherCsvFileManager voucherCsvFileManager; - - public VoucherFileRepository(VoucherCsvFileManager voucherCsvFileManager) { - log.debug("FileVoucherRepository started."); - - this.voucherCsvFileManager = voucherCsvFileManager; - } - - @Override - public VoucherPolicy findById(UUID voucherId) { - return Optional.ofNullable(vouchers.get(voucherId)) - .orElseThrow(NoSuchElementException::new); - } - - @Override - public List findAll() { - return new ArrayList<>(vouchers.values()); - } - - @Override - public VoucherPolicy create(VoucherPolicy voucherPolicy) { - vouchers.putIfAbsent(voucherPolicy.getVoucherId(), voucherPolicy); - return voucherPolicy; - } - - @PostConstruct - private void fileRead(){ - List voucherPolicies = voucherCsvFileManager.read(); - voucherPolicies.forEach(this::create); - } - - @PreDestroy - private void fileWrite(){ - voucherCsvFileManager.write(this.findAll()); - } -} - -// 데코레이터 패턴: 기능의 확장. 이것이 기능의 확장이라 보기는 어렵다. -// 빈은 싱글톤이다. 만약 미래에 VoucherFileRepository와 VoucherMemoryRepository를 둘 다 쓴다면 -// VoucherFileRepository에서 VoucherMemoryRepository 내 HashMap을 쓸 건데 싱글톤이라 결국 둘을 공존해 쓰게 되고 이로 인한 이슈가 있을 수 있다. -// 둘을 분리하려면 new로 따로 인스턴스 생성해서 넣어줘도 해결을 할 수 있지만 이를 인지하면서 개발하는 것부터 스트레스임. -// 그냥 분리하자. diff --git a/src/main/java/org/prgms/springbootbasic/repository/VoucherMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/VoucherMemoryRepository.java deleted file mode 100644 index 458f3ce52f..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/VoucherMemoryRepository.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import lombok.extern.slf4j.Slf4j; -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Repository; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -@Repository -@Profile({"dev", "prod", "local", "test"}) -@Slf4j -public class VoucherMemoryRepository implements VoucherRepository{ - ConcurrentHashMap mem = new ConcurrentHashMap<>(); - - @Override - public VoucherPolicy findById(UUID voucherId) { - return Optional.ofNullable(mem.get(voucherId)) - .orElseThrow(NoSuchElementException::new); - } - - @Override - public List findAll() { - return new ArrayList<>(mem.values()); - } - - @Override - public VoucherPolicy create(VoucherPolicy voucherPolicy) { - mem.putIfAbsent(voucherPolicy.getVoucherId(), voucherPolicy); - return voucherPolicy; - } -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/VoucherRepository.java b/src/main/java/org/prgms/springbootbasic/repository/VoucherRepository.java deleted file mode 100644 index 2c77f22364..0000000000 --- a/src/main/java/org/prgms/springbootbasic/repository/VoucherRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.prgms.springbootbasic.repository; - -import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; - -import java.util.List; -import java.util.UUID; - -public interface VoucherRepository { - VoucherPolicy findById(UUID voucherId); - List findAll(); - VoucherPolicy create(VoucherPolicy voucherPolicy); -} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerFileRepository.java new file mode 100644 index 0000000000..05e4bed3f1 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerFileRepository.java @@ -0,0 +1,61 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.prgms.springbootbasic.common.file.CustomerCsvFileManager; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@Profile({"test"}) +public class CustomerFileRepository implements CustomerRepository { // 추후 구현 + private final CustomerCsvFileManager customerCsvFileManager; + + public CustomerFileRepository(CustomerCsvFileManager customerCsvFileManager) { + this.customerCsvFileManager = customerCsvFileManager; + } + + @Override + public Customer upsert(Customer customer) { + return null; + } + + @Override + public Optional findById(UUID customerId) { + return Optional.empty(); + } + + @Override + public Optional findByEmail(String email) { + return Optional.empty(); + } + + @Override + public List findBetweenLocalDateTime(LocalDateTime start, LocalDateTime end) { + return null; + } + + @Override + public List findAll() { + return null; + } + + @Override + public List findBlackAll() { + return customerCsvFileManager.readBlack(); + } + + @Override + public void deleteById(UUID customerId) { + + } + + @Override + public int deleteAll() { + return 0; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepository.java new file mode 100644 index 0000000000..37cc3556aa --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepository.java @@ -0,0 +1,121 @@ +package org.prgms.springbootbasic.repository.customer; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.common.UtilMethod; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.*; + +@Repository +@Slf4j +@Profile({"dev", "prod"}) +public class CustomerJdbcRepository implements CustomerRepository{ + private final NamedParameterJdbcTemplate jdbcTemplate; + + public CustomerJdbcRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Customer upsert(Customer customer) { + Optional foundCustomer = findById(customer.getCustomerId()); + + if (foundCustomer.isPresent()){ + jdbcTemplate.update("UPDATE customers SET name = :name, is_blacked = :isBlacked WHERE customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + toParamMap(customer)); + } else { + jdbcTemplate.update("INSERT INTO customers(customer_id, name, email, is_blacked, created_at) VALUES (UNHEX(REPLACE(:customerId, '-', '')), :name, :email, :isBlacked, :createdAt)", + toParamMap(customer)); + } + + return customer; + } + + @Override + public Optional findById(UUID customerId) { + try { + return Optional.ofNullable( + jdbcTemplate.queryForObject("SELECT * FROM customers WHERE customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + Collections.singletonMap("customerId", customerId.toString().getBytes()), + mapToCustomer)); + } catch (EmptyResultDataAccessException e) { + log.info("customerId에 해당하는 customer가 DB에 없음."); + return Optional.empty(); + } + } + + @Override + public Optional findByEmail(String email) { + try { + return Optional.ofNullable( + jdbcTemplate.queryForObject("SELECT * FROM customers WHERE email = :email", + Collections.singletonMap("email", email), + mapToCustomer)); + } catch (EmptyResultDataAccessException e) { + log.info("email에 해당하는 customer가 DB에 없음."); + return Optional.empty(); + } + } + + @Override + public List findBetweenLocalDateTime(LocalDateTime start, LocalDateTime end) { + try { + return jdbcTemplate.query("SELECT * FROM customers WHERE created_at >= :start AND created_at <= :end", + Map.of("start", start, "end", end), + mapToCustomer); + } catch (DataAccessException e) { + log.error("날짜 범위 내 고객 찾기 중 에러."); + return new ArrayList<>(); + } + } + + @Override + public List findAll() { + return jdbcTemplate.query("SELECT * FROM customers", mapToCustomer); + } + + @Override + public List findBlackAll() { + return jdbcTemplate.query("SELECT * FROM customers WHERE is_blacked = true", mapToCustomer); + } + + @Override + public void deleteById(UUID customerId) { + jdbcTemplate.update("DELETE FROM customers WHERE customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + Collections.singletonMap("customerId", customerId.toString().getBytes())); + } + + @Override + public int deleteAll() { + return jdbcTemplate.update("DELETE FROM customers", Collections.emptyMap()); + } + + private static Map toParamMap(Customer customer) { + return new HashMap<>(){{ + put("customerId", customer.getCustomerId().toString().getBytes()); + put("name", customer.getName()); + put("email", customer.getEmail()); + put("isBlacked", customer.isBlacked()); + put("createdAt", customer.getCreatedAt()); + }}; + } + + private static RowMapper mapToCustomer = (rs, rowNum) -> { + String customerName = rs.getString("name"); + UUID customerId = UtilMethod.bytesToUUID(rs.getBytes("customer_id")); + String email = rs.getString("email"); + LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime(); + LocalDateTime lastLoginAt = rs.getTimestamp("last_login_at") != null + ? rs.getTimestamp("last_login_at").toLocalDateTime() : null; + boolean isBlacked = rs.getBoolean("is_blacked"); + + return new Customer(customerId, customerName, email, createdAt, lastLoginAt, isBlacked); + }; +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerMemoryRepository.java new file mode 100644 index 0000000000..a8c99a4fe1 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerMemoryRepository.java @@ -0,0 +1,55 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.prgms.springbootbasic.domain.customer.Customer; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +@Profile({"local"}) +public class CustomerMemoryRepository implements CustomerRepository { // 추후 구현 + @Override + public Customer upsert(Customer customer) { + return null; + } + + @Override + public Optional findById(UUID customerId) { + return Optional.empty(); + } + + @Override + public Optional findByEmail(String email) { + return Optional.empty(); + } + + @Override + public List findBetweenLocalDateTime(LocalDateTime start, LocalDateTime end) { + return null; + } + + @Override + public List findAll() { + return null; + } + + @Override + public List findBlackAll() { + return new ArrayList<>(); + } + + @Override + public void deleteById(UUID customerId) { + + } + + @Override + public int deleteAll() { + return 0; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerRepository.java new file mode 100644 index 0000000000..bab82951e4 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customer/CustomerRepository.java @@ -0,0 +1,19 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.prgms.springbootbasic.domain.customer.Customer; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface CustomerRepository { + Customer upsert(Customer customer); + Optional findById(UUID customerId); + Optional findByEmail(String email); + List findBetweenLocalDateTime(LocalDateTime start, LocalDateTime end); + List findAll(); + List findBlackAll(); + void deleteById(UUID customerId); + int deleteAll(); +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepository.java new file mode 100644 index 0000000000..2e376dd00c --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepository.java @@ -0,0 +1,94 @@ +package org.prgms.springbootbasic.repository.customervouchermanagement; + +import org.prgms.springbootbasic.common.UtilMethod; +import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.prgms.springbootbasic.common.UtilMethod.bytesToUUID; + +@Repository +public class CustomerVoucherManagementJdbcRepository implements CustomerVoucherManagementRepository { + private final NamedParameterJdbcTemplate jdbcTemplate; + + public CustomerVoucherManagementJdbcRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public void allocateVoucherById(UUID customerId, UUID voucherId) { + jdbcTemplate.update("INSERT INTO customers_vouchers VALUES (UNHEX(REPLACE(:customerId, '-', '')), UNHEX(REPLACE(:voucherId, '-', '')))", + toParamMap(customerId, voucherId)); + } + + @Override + public void deleteVoucherById(UUID customerId, UUID voucherId) { + jdbcTemplate.update("DELETE FROM customers_vouchers " + + "WHERE customer_id = UNHEX(REPLACE(:customerId, '-', '')) AND voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + toParamMap(customerId, voucherId)); + } + + @Override + public void deleteAll() { + jdbcTemplate.update("DELETE FROM customers_vouchers", Collections.emptyMap()); + } + + @Override + public List searchVouchersByCustomerId(UUID customerId) { + return jdbcTemplate.query("SELECT v.voucher_id, v.discount_degree, v.voucher_type " + + "FROM vouchers v JOIN customers_vouchers w ON v.voucher_id = w.voucher_id " + + "WHERE w.customer_id = UNHEX(REPLACE(:customerId, '-', ''))", + Collections.singletonMap("customerId", customerId.toString().getBytes()), + mapToVoucher); + } + + @Override + public List searchCustomersByVoucherId(UUID voucherId) { + return jdbcTemplate.query("SELECT c.customer_id, c.name, c.email, c.last_login_at, c.created_at, c.is_blacked " + + "FROM customers c " + + "JOIN customers_vouchers w ON c.customer_id = w.customer_id " + + "WHERE w.voucher_id = UUID_TO_BIN(:voucherId)", + Collections.singletonMap("voucherId", voucherId.toString().getBytes()), + mapToCustomer); + } + + private static RowMapper mapToVoucher = (rs, rowNum) -> { + UUID voucherId = bytesToUUID(rs.getBytes("voucher_id")); + long discountDegree = rs.getLong("discount_degree"); + String voucherTypeString = rs.getString("voucher_type"); + VoucherType voucherType = Arrays.stream(VoucherType.values()) + .filter(vt -> vt.getDisplayName().equals(voucherTypeString)) + .findAny() + .orElseThrow(() -> new NoSuchElementException("해당 VoucherType이 존재하지 않음.")); + VoucherPolicy voucherPolicy = voucherType.create(); + + return new Voucher(voucherId, discountDegree, voucherPolicy); + }; + + private static RowMapper mapToCustomer = (rs, rowNum) -> { + String customerName = rs.getString("name"); + UUID customerId = UtilMethod.bytesToUUID(rs.getBytes("customer_id")); + String email = rs.getString("email"); + LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime(); + LocalDateTime lastLoginAt = rs.getTimestamp("last_login_at") != null + ? rs.getTimestamp("last_login_at").toLocalDateTime() : null; + boolean isBlacked = rs.getBoolean("is_blacked"); + + return new Customer(customerId, customerName, email, lastLoginAt, createdAt, isBlacked); + }; // static 메서드의 위치 조정. + + private Map toParamMap(UUID customerId, UUID voucherId){ + return new HashMap<>(){{ + put("customerId", customerId.toString().getBytes()); + put("voucherId", voucherId.toString().getBytes()); + }}; + } + +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementRepository.java b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementRepository.java new file mode 100644 index 0000000000..a6ca57401f --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementRepository.java @@ -0,0 +1,15 @@ +package org.prgms.springbootbasic.repository.customervouchermanagement; + +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; + +import java.util.List; +import java.util.UUID; + +public interface CustomerVoucherManagementRepository { + void allocateVoucherById(UUID customerId, UUID voucherId); + void deleteVoucherById(UUID customerId, UUID voucherId); + void deleteAll(); + List searchVouchersByCustomerId(UUID customerId); + List searchCustomersByVoucherId(UUID voucherId); +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepository.java new file mode 100644 index 0000000000..e9bdccf109 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepository.java @@ -0,0 +1,78 @@ +package org.prgms.springbootbasic.repository.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.common.file.VoucherCsvFileManager; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@Profile({"test"}) +@Primary +@Slf4j +public class VoucherCsvFileRepository implements VoucherRepository { + private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); + private final VoucherCsvFileManager voucherCsvFileManager; + + public VoucherCsvFileRepository(VoucherCsvFileManager voucherCsvFileManager) { + log.debug("FileVoucherRepository started."); + + this.voucherCsvFileManager = voucherCsvFileManager; + } + + @Override + public Optional findById(UUID voucherId) { + return Optional.ofNullable(vouchers.get(voucherId)); + } + + @Override + public List findByPolicyBetweenLocalDateTime(String voucherPolicy, LocalDateTime startOfDay, LocalDateTime endOfDay) { + return null; + } + + @Override + public List findAll() { + return new ArrayList<>(vouchers.values()); + } + + @Override + public Voucher upsert(Voucher voucher) { + if (Optional.ofNullable(vouchers.get(voucher.getVoucherId())).isPresent()) { + vouchers.replace(voucher.getVoucherId(), voucher); + } else { + vouchers.put(voucher.getVoucherId(), voucher); + } + return voucher; + } + + @Override + public void deleteById(UUID voucherId) { + vouchers.remove(voucherId); + } + + @Override + public void deleteAll() { + vouchers.clear(); + } + + @PostConstruct + private void fileRead(){ + List voucherPolicies = voucherCsvFileManager.read(); + voucherPolicies.forEach(this::upsert); + } + + @PreDestroy + private void fileWrite(){ + voucherCsvFileManager.write(this.findAll()); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepository.java new file mode 100644 index 0000000000..f2c351c8ec --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepository.java @@ -0,0 +1,109 @@ +package org.prgms.springbootbasic.repository.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; +import org.springframework.context.annotation.Profile; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.*; + +import static org.prgms.springbootbasic.common.UtilMethod.bytesToUUID; + +@Repository +@Slf4j +@Profile({"dev", "prod"}) +public class VoucherJdbcRepository implements VoucherRepository { // 네이밍 고민 + private final NamedParameterJdbcTemplate jdbcTemplate; + + public VoucherJdbcRepository(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + @Override + public Voucher upsert(Voucher voucher) { + Optional foundVoucher = findById(voucher.getVoucherId()); // 재사용 지양 + + if (foundVoucher.isPresent()){ + jdbcTemplate.update("UPDATE vouchers SET discount_degree = :discountDegree, voucher_type = :voucherType WHERE voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + toParamMap(voucher)); + } else { + jdbcTemplate.update("INSERT INTO vouchers(voucher_id, discount_degree, voucher_type) " + + "VALUES (UNHEX(REPLACE(:voucherId, '-', '')), :discountDegree, :voucherType)", + toParamMap(voucher)); + } + + return voucher; + } + + @Override + public Optional findById(UUID voucherId) { + try { + return Optional.ofNullable( + jdbcTemplate.queryForObject("SELECT * FROM vouchers WHERE voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + Collections.singletonMap("voucherId", voucherId.toString().getBytes()), + mapToVoucher)); + } catch (EmptyResultDataAccessException e) { + log.info("voucherId에 해당하는 Voucher가 DB에 없음."); + return Optional.empty(); + } + } + + @Override + public List findByPolicyBetweenLocalDateTime(String voucherPolicy, LocalDateTime startOfDay, LocalDateTime endOfDay) { + try { + return jdbcTemplate.query("SELECT * FROM vouchers WHERE voucher_type LIKE :voucherPolicy AND created_at >= :start AND created_at <= :end", + Map.of("voucherPolicy", voucherPolicy, "start", startOfDay, "end", endOfDay), + mapToVoucher); + } catch (DataAccessException e) { + log.error("바우처를 가져오는데 예외 발생"); + return new ArrayList<>(); + } + } + + @Override + public List findAll() { + return jdbcTemplate.query("SELECT * FROM vouchers", mapToVoucher); + } + + @Override + public void deleteById(UUID voucherId) { + jdbcTemplate.update("DELETE FROM vouchers WHERE voucher_id = UNHEX(REPLACE(:voucherId, '-', ''))", + Collections.singletonMap("voucherId", voucherId.toString().getBytes())); // 예외 명시적으로 던지기. update가 던져주는 예외는 이해하기 어려울 수도. + } + + @Override + public void deleteAll() { + jdbcTemplate.update("DELETE FROM vouchers", Collections.emptyMap()); + } + + private static Map toParamMap(Voucher voucher) { + return new HashMap<>(){{ + put("voucherId", voucher.getVoucherId().toString().getBytes()); + put("discountDegree", voucher.getDiscountDegree()); + put("voucherType", voucher.getVoucherPolicy().getClass().getSimpleName()); + }}; + } + + private static RowMapper mapToVoucher = (rs, rowNum) -> { + UUID voucherId = bytesToUUID(rs.getBytes("voucher_id")); + long discountDegree = rs.getLong("discount_degree"); + String voucherTypeString = rs.getString("voucher_type"); + LocalDateTime createdAt = rs.getTimestamp("created_at").toLocalDateTime(); + VoucherType voucherType = Arrays.stream(VoucherType.values()) + .filter(vt -> vt.getDisplayName().equals(voucherTypeString)) + .findAny() + .orElseThrow(() -> new NoSuchElementException("해당 VoucherType이 존재하지 않음.")); + VoucherPolicy voucherPolicy = voucherType.create(); + + return new Voucher(voucherId, discountDegree, voucherPolicy, createdAt); + }; + + +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepository.java new file mode 100644 index 0000000000..7d074f79db --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepository.java @@ -0,0 +1,55 @@ +package org.prgms.springbootbasic.repository.voucher; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Repository +@Profile({"local"}) +@Slf4j +public class VoucherMemoryRepository implements VoucherRepository{ + private final ConcurrentHashMap vouchers = new ConcurrentHashMap<>(); + + @Override + public Optional findById(UUID voucherId) { + return Optional.ofNullable(vouchers.get(voucherId)); + } + + @Override + public List findByPolicyBetweenLocalDateTime(String voucherPolicy, LocalDateTime startOfDay, LocalDateTime endOfDay) { + return null; + } + + @Override + public List findAll() { + return new ArrayList<>(vouchers.values()); + } + + @Override + public Voucher upsert(Voucher voucher) { + if (Optional.ofNullable(vouchers.get(voucher.getVoucherId())).isPresent()) { + vouchers.replace(voucher.getVoucherId(), voucher); + } else { + vouchers.put(voucher.getVoucherId(), voucher); + } + return voucher; + } + + @Override + public void deleteById(UUID voucherId) { + vouchers.remove(voucherId); + } + + @Override + public void deleteAll() { + vouchers.clear(); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherRepository.java b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherRepository.java new file mode 100644 index 0000000000..14e7193b7f --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/repository/voucher/VoucherRepository.java @@ -0,0 +1,17 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.prgms.springbootbasic.domain.voucher.Voucher; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public interface VoucherRepository { + Optional findById(UUID voucherId); + List findByPolicyBetweenLocalDateTime(String voucherPolicy, LocalDateTime startOfDay, LocalDateTime endOfDay); + List findAll(); + Voucher upsert(Voucher voucher); + void deleteById(UUID voucherId); + void deleteAll(); +} diff --git a/src/main/java/org/prgms/springbootbasic/service/CustomerService.java b/src/main/java/org/prgms/springbootbasic/service/CustomerService.java index 35bd35fc46..2f38792633 100644 --- a/src/main/java/org/prgms/springbootbasic/service/CustomerService.java +++ b/src/main/java/org/prgms/springbootbasic/service/CustomerService.java @@ -2,10 +2,14 @@ import lombok.extern.slf4j.Slf4j; import org.prgms.springbootbasic.domain.customer.Customer; -import org.prgms.springbootbasic.repository.CustomerRepository; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.prgms.springbootbasic.repository.customer.CustomerRepository; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service @Slf4j @@ -16,6 +20,39 @@ public CustomerService(CustomerRepository customerRepository) { this.customerRepository = customerRepository; } + public Customer insert(String name, String email) { + Customer customer = new Customer(UUID.randomUUID(), name, email, LocalDateTime.now()); + + return this.customerRepository.upsert(customer); + } + + public Customer update(UUID customerId, String name, boolean isBlacked) { + Customer customer = findById(customerId).orElseThrow(EntityNotFoundException::new); + Customer updatedCustomer = customer.changeInfo(name, isBlacked); + + return customerRepository.upsert(updatedCustomer); + } + + public Optional findById(UUID customerId){ + return customerRepository.findById(customerId); + } + + public List findBetweenLocalDateTime(LocalDateTime start, LocalDateTime end) { + return customerRepository.findBetweenLocalDateTime(start, end); + } + + public List findAll() { + return customerRepository.findAll(); + } + + public void deleteById(UUID customerId) { + customerRepository.deleteById(customerId); + } + + public void deleteAll(){ + customerRepository.deleteAll(); + } + public List findBlackAll(){ return customerRepository.findBlackAll(); } diff --git a/src/main/java/org/prgms/springbootbasic/service/CustomerVoucherManagementService.java b/src/main/java/org/prgms/springbootbasic/service/CustomerVoucherManagementService.java new file mode 100644 index 0000000000..7dc1b42b03 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/CustomerVoucherManagementService.java @@ -0,0 +1,36 @@ +package org.prgms.springbootbasic.service; + +import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.repository.customervouchermanagement.CustomerVoucherManagementRepository; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.UUID; + +@Service +@Slf4j +public class CustomerVoucherManagementService { + private final CustomerVoucherManagementRepository managementRepository; + + public CustomerVoucherManagementService(CustomerVoucherManagementRepository managementRepository) { + this.managementRepository = managementRepository; + } + + public void allocate(UUID customerId, UUID voucherId){ + managementRepository.allocateVoucherById(customerId, voucherId); + } + + public void delete(UUID customerId, UUID voucherId){ + managementRepository.deleteVoucherById(customerId, voucherId); + } + + public List searchVouchersFromCustomer(UUID customerId){ + return managementRepository.searchVouchersByCustomerId(customerId); + } + + public List searchCustomerFromVoucher(UUID voucherId){ + return managementRepository.searchCustomersByVoucherId(voucherId); + } +} diff --git a/src/main/java/org/prgms/springbootbasic/service/VoucherService.java b/src/main/java/org/prgms/springbootbasic/service/VoucherService.java index 96d5246de9..b75cb5e0a2 100644 --- a/src/main/java/org/prgms/springbootbasic/service/VoucherService.java +++ b/src/main/java/org/prgms/springbootbasic/service/VoucherService.java @@ -1,12 +1,20 @@ package org.prgms.springbootbasic.service; import lombok.extern.slf4j.Slf4j; +import org.prgms.springbootbasic.service.dto.VoucherFilterDto; +import org.prgms.springbootbasic.service.dto.VoucherInsertDto; +import org.prgms.springbootbasic.service.dto.VoucherResponseDto; import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.domain.voucher.Voucher; import org.prgms.springbootbasic.domain.voucher.VoucherPolicy; -import org.prgms.springbootbasic.repository.VoucherRepository; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.prgms.springbootbasic.repository.voucher.VoucherRepository; +import org.prgms.springbootbasic.service.dto.VoucherUpdateDto; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; +import java.util.UUID; @Service @Slf4j @@ -17,17 +25,60 @@ public VoucherService(VoucherRepository voucherRepository) { this.voucherRepository = voucherRepository; } - public VoucherType seqToType(int voucherSeq) { + public VoucherType convertToType(int voucherSeq) { return VoucherType.getTypeFromSeq(voucherSeq); } - public void create(VoucherType voucherType, int discountDegree) { - VoucherPolicy voucherPolicy = voucherType.create(discountDegree); // 생성 비즈니스 로직이 있다. + public VoucherType convertToType(String policyName) { + return VoucherType.getTypeFromName(policyName); + } + + public VoucherResponseDto insert(VoucherInsertDto voucherInsertDto) { + VoucherPolicy voucherPolicy = voucherInsertDto.voucherType().create(); + Voucher voucher = new Voucher(UUID.randomUUID(), voucherInsertDto.discountDegree(), voucherPolicy); + Voucher upsertedVoucher = voucherRepository.upsert(voucher); + + return VoucherResponseDto.convertVoucherToVoucherResponseDto(upsertedVoucher); + } + + public VoucherResponseDto update(VoucherUpdateDto voucherUpdateDto) { + UUID voucherId = voucherUpdateDto.voucherId(); + + findById(voucherId).orElseThrow(EntityNotFoundException::new); + + Voucher updateVoucher = new Voucher(voucherId, + voucherUpdateDto.discountDegree(), + voucherUpdateDto.voucherType().create()); + Voucher upsertedVoucher = voucherRepository.upsert(updateVoucher); + + return VoucherResponseDto.convertVoucherToVoucherResponseDto(upsertedVoucher); + } + + public Optional findById(UUID voucherId){ + Optional foundVoucher = voucherRepository.findById(voucherId); + return foundVoucher.map(VoucherResponseDto::convertVoucherToVoucherResponseDto); + } + + public List findByPolicyBetweenLocalDateTime(VoucherFilterDto voucherFilterDto) { + List filteredVouchers = voucherRepository.findByPolicyBetweenLocalDateTime(voucherFilterDto.voucherType().getDisplayName(), + voucherFilterDto.startDay(), + voucherFilterDto.endDay()); + + return filteredVouchers.stream() + .map(VoucherResponseDto::convertVoucherToVoucherResponseDto).toList(); + } + + public List findAll(){ + List vouchers = voucherRepository.findAll(); + return vouchers.stream() + .map(VoucherResponseDto::convertVoucherToVoucherResponseDto).toList(); + } - voucherRepository.create(voucherPolicy); + public void deleteById(UUID voucherId) { + voucherRepository.deleteById(voucherId); } - public List findAll(){ - return voucherRepository.findAll(); + public void deleteAll() { + voucherRepository.deleteAll(); } } diff --git a/src/main/java/org/prgms/springbootbasic/service/dto/VoucherCreateRequestDto.java b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherCreateRequestDto.java new file mode 100644 index 0000000000..39b5fe862d --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherCreateRequestDto.java @@ -0,0 +1,6 @@ +package org.prgms.springbootbasic.service.dto; + +import org.prgms.springbootbasic.domain.VoucherType; + +public record VoucherCreateRequestDto(VoucherType voucherPolicy, long discountDegree) { +} diff --git a/src/main/java/org/prgms/springbootbasic/service/dto/VoucherFilterDto.java b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherFilterDto.java new file mode 100644 index 0000000000..c3b9385eb5 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherFilterDto.java @@ -0,0 +1,18 @@ +package org.prgms.springbootbasic.service.dto; + +import org.prgms.springbootbasic.domain.VoucherType; + +import java.time.LocalDateTime; + +public record VoucherFilterDto(LocalDateTime startDay, + LocalDateTime endDay, + VoucherType voucherType) { + @Override + public String toString() { + return "VoucherFilterDto{" + + "startDay=" + startDay + + ", endDay=" + endDay + + ", voucherType=" + voucherType.getDisplayName() + + '}'; + } +} diff --git a/src/main/java/org/prgms/springbootbasic/service/dto/VoucherInsertDto.java b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherInsertDto.java new file mode 100644 index 0000000000..30a506208e --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherInsertDto.java @@ -0,0 +1,7 @@ +package org.prgms.springbootbasic.service.dto; + +import org.prgms.springbootbasic.domain.VoucherType; + +public record VoucherInsertDto(VoucherType voucherType, + long discountDegree) { +} diff --git a/src/main/java/org/prgms/springbootbasic/service/dto/VoucherResponseDto.java b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherResponseDto.java new file mode 100644 index 0000000000..27353e30e8 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherResponseDto.java @@ -0,0 +1,20 @@ +package org.prgms.springbootbasic.service.dto; + +import org.prgms.springbootbasic.domain.voucher.Voucher; + +import java.time.LocalDateTime; +import java.util.UUID; + +public record VoucherResponseDto(UUID voucherId, + long discountDegree, + String voucherPolicy, + LocalDateTime createdAt) { + + public static VoucherResponseDto convertVoucherToVoucherResponseDto(Voucher voucher) { + return new VoucherResponseDto(voucher.getVoucherId(), + voucher.getDiscountDegree(), + voucher.getVoucherPolicy().getClass().getSimpleName(), + voucher.getCreatedAt()); + } // Dto -> voucher. 1. dto에서 정적 팩토리 메서드로. 2. 매퍼 클래스를 만드는거. 클래스 역할과 책임에 따라. 의존관계에 따른 고민. + // 이 애플리케이션은 간단하니 Mapper 클래스를 따로 정하지 않기로 결정. +} diff --git a/src/main/java/org/prgms/springbootbasic/service/dto/VoucherUpdateDto.java b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherUpdateDto.java new file mode 100644 index 0000000000..f974e4e0e2 --- /dev/null +++ b/src/main/java/org/prgms/springbootbasic/service/dto/VoucherUpdateDto.java @@ -0,0 +1,10 @@ +package org.prgms.springbootbasic.service.dto; + +import org.prgms.springbootbasic.domain.VoucherType; + +import java.util.UUID; + +public record VoucherUpdateDto(UUID voucherId, + VoucherType voucherType, + long discountDegree) { +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000000..057274fbff --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,6 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/test + username: test + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000000..afd1b972b9 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,3 @@ +basic: + file: + path: ./src/main/resources/voucher.csv diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000000..64c9e21ad4 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,6 @@ +spring: + datasource: + url: jdbc:mysql://localhost:3306/prod + username: root + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000000..46d45d35fd --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,9 @@ +basic: + file: + path: ./src/test/resources/voucher.csv +spring: + datasource: + url: jdbc:mysql://localhost:3306/test + username: test + password: 1234 + driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000000..866492b285 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,23 @@ +CREATE TABLE IF NOT EXISTS customers ( + customer_id BINARY(16) PRIMARY KEY, + name VARCHAR(26) NOT NULL, + email VARCHAR(56) NOT NULL , + last_login_at DATETIME(6) DEFAULT NULL, + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + is_blacked BOOLEAN NOT NULL DEFAULT FALSE, + CONSTRAINT unq_user_email UNIQUE (email) +); + +CREATE TABLE IF NOT EXISTS vouchers ( + voucher_id BINARY(16) PRIMARY KEY, + discount_degree bigint NOT NULL, + voucher_type varchar(64) NOT NULL + created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) +); + +CREATE TABLE IF NOT EXISTS customers_vouchers ( -- 이 이름이 맞는지? 이건 wallet이라기 보다는 그냥 고객-바우처 매핑 테이블인데? + customer_id BINARY(16) NOT NULL, + voucher_id BINARY(16) NOT NULL, + FOREIGN KEY (customer_id) references customers(customer_id), + FOREIGN KEY (voucher_id) references vouchers(voucher_id) +); diff --git a/src/main/resources/templates/customer/customer-create.html b/src/main/resources/templates/customer/customer-create.html new file mode 100644 index 0000000000..4e93a8b35d --- /dev/null +++ b/src/main/resources/templates/customer/customer-create.html @@ -0,0 +1,30 @@ + + + + + + + + 고객 생성 페이지 + + + +
+
+
+ + +
+
+ + +
+ +
+ + + +
+
+ + diff --git a/src/main/resources/templates/customer/customer-details.html b/src/main/resources/templates/customer/customer-details.html new file mode 100644 index 0000000000..7d175aff82 --- /dev/null +++ b/src/main/resources/templates/customer/customer-details.html @@ -0,0 +1,68 @@ + + + + + + + + 고객 상세 페이지 + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

블랙 여부

+ + +
+ +
+ + + + +
+
+ + + diff --git a/src/main/resources/templates/customer/customer-list.html b/src/main/resources/templates/customer/customer-list.html new file mode 100644 index 0000000000..43358c6ed3 --- /dev/null +++ b/src/main/resources/templates/customer/customer-list.html @@ -0,0 +1,50 @@ + + +
+ + + + + + + + + + + + + + + + +
고객 ID이름이메일
+
+

고객 정보가 없습니다!

+
+ + + +
+ diff --git a/src/main/resources/templates/header.html b/src/main/resources/templates/header.html new file mode 100644 index 0000000000..579d868484 --- /dev/null +++ b/src/main/resources/templates/header.html @@ -0,0 +1,25 @@ + + +
+
+ +
+
+ diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html new file mode 100644 index 0000000000..024068e3ab --- /dev/null +++ b/src/main/resources/templates/index.html @@ -0,0 +1,47 @@ + + + + + + + + Spring Boot Basic 과제 홈 페이지 + + + +
+
+ +
+
+
+
+
+
+
+
+ + + + diff --git a/src/main/resources/templates/voucher/voucher-create.html b/src/main/resources/templates/voucher/voucher-create.html new file mode 100644 index 0000000000..287c042c23 --- /dev/null +++ b/src/main/resources/templates/voucher/voucher-create.html @@ -0,0 +1,35 @@ + + + + + + + + 바우처 생성 페이지 + + + +
+
+
+ + +
+
+ + +
+ +
+ + + +
+
+ + diff --git a/src/main/resources/templates/voucher/voucher-details.html b/src/main/resources/templates/voucher/voucher-details.html new file mode 100644 index 0000000000..25f06ca70e --- /dev/null +++ b/src/main/resources/templates/voucher/voucher-details.html @@ -0,0 +1,47 @@ + + + + + + + + 고객 상세 페이지 + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + + +
+
+ + + diff --git a/src/main/resources/templates/voucher/voucher-list.html b/src/main/resources/templates/voucher/voucher-list.html new file mode 100644 index 0000000000..1c1f33d26d --- /dev/null +++ b/src/main/resources/templates/voucher/voucher-list.html @@ -0,0 +1,50 @@ + + +
+ + + + + + + + + + + + + + + + +
바우처 ID할인 정도할인 정책
+
+

바우처 정보가 없습니다!

+
+ + + +
+ diff --git a/src/test/java/org/prgms/springbootbasic/controller/voucher/VoucherApiControllerTest.java b/src/test/java/org/prgms/springbootbasic/controller/voucher/VoucherApiControllerTest.java new file mode 100644 index 0000000000..afe187bbc6 --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/controller/voucher/VoucherApiControllerTest.java @@ -0,0 +1,143 @@ +package org.prgms.springbootbasic.controller.voucher; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.service.dto.VoucherCreateRequestDto; +import org.prgms.springbootbasic.service.dto.VoucherInsertDto; +import org.prgms.springbootbasic.service.dto.VoucherResponseDto; +import org.prgms.springbootbasic.domain.VoucherType; +import org.prgms.springbootbasic.service.VoucherService; +import org.prgms.springbootbasic.service.dto.VoucherFilterDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(controllers = VoucherApiController.class) +@ActiveProfiles("dev") +@Slf4j +class VoucherApiControllerTest { // 컨트롤러 유닛 테스트로. (죄다 나머지 목 객체) + @Autowired + private MockMvc mockMvc; + @MockBean + private VoucherService voucherService; + private final String uri = "/api/v1/vouchers"; + @Autowired + private ObjectMapper objectMapper; + + // 예외는 컨트롤러 어드바이스에서 함께 처리할 수 있음. REST 공부하기 깊은 이해로. MVC 의존관계. DTO 관련 어디에 둬야 할지. DTO가 뭔지 깊은 이해. +// 단위 테스트만의 장점: fixed 들어왔을때 에러 뱉어야해 테스트 혹은 데이터 설정, 통합은 느림. +// 단위는 빠름. 원하는 결과를 해당 부분만 독립적으로 테스트가 가능. 검증 쉽고. + // 다음 플젝에서는 이를 전부 반영하자. + // 이 과제 피드백도 반영 후 이유를 적어 올리자. + @Test + @DisplayName("특정 기준으로 필터링된 바우처들을 조회할 수 있다.") + void getVouchersFilteredByQueryParameter() throws Exception { + LocalDateTime startDay = LocalDateTime.of(2022, 11, 1, 0, 0); + LocalDateTime endDay = LocalDateTime.of(2023, 11, 1, 0, 0); + + VoucherFilterDto voucherFilterDto = new VoucherFilterDto(startDay, endDay, VoucherType.FIXED_AMOUNT); + + log.info(voucherFilterDto.toString()); + + when(voucherService.findByPolicyBetweenLocalDateTime(eq(voucherFilterDto))) + .thenReturn(List.of(new VoucherResponseDto(UUID.randomUUID(), + 1000, + "FixedAmountPolicy", + LocalDateTime.of(2023, 6, 1, 11,10)), + new VoucherResponseDto(UUID.randomUUID(), + 2000, + "FixedAmountPolicy", + LocalDateTime.of(2023, 8, 1, 18, 20)) + )); + when(voucherService.convertToType("FixedAmountPolicy")) + .thenReturn(VoucherType.FIXED_AMOUNT); + + this.mockMvc.perform(get(uri) + .param("startDay", startDay.toString()) + .param("endDay", endDay.toString()) + .param("voucherPolicy", "FixedAmountPolicy") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.length()").value(2)); + + verify(voucherService).findByPolicyBetweenLocalDateTime(eq(voucherFilterDto)); + } + + @Test + @DisplayName("JSON으로 포맷된 특정 바우처 세부 정보를 조회할 수 있다.") + void showVoucherInDetailsFormattedByJSON() throws Exception { + UUID setUpVoucherId = UUID.randomUUID(); + + when(voucherService.findById(setUpVoucherId)) + .thenReturn(Optional.of(new VoucherResponseDto(setUpVoucherId, + 1000, + "FixedAmountPolicy", + LocalDateTime.now()))); + + this.mockMvc.perform(get(uri + "/{id}", setUpVoucherId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.voucherId").value(setUpVoucherId.toString())) + .andExpect(jsonPath("$.discountDegree").value(1000)) + .andExpect(jsonPath("$.voucherPolicy").value("FixedAmountPolicy")); + + verify(voucherService).findById(setUpVoucherId); + } + + @Test + @DisplayName("바우처 생성 로직을 실행할 수 있다.") + void callCreateVoucher() throws Exception { + VoucherCreateRequestDto voucherCreateRequestDto = new VoucherCreateRequestDto(VoucherType.PERCENT_DISCOUNT, 20); + + when(voucherService.insert(any(VoucherInsertDto.class))) + .thenReturn(new VoucherResponseDto(UUID.randomUUID(), + 20, + VoucherType.PERCENT_DISCOUNT.getDisplayName(), + LocalDateTime.now())); + + mockMvc.perform(post(uri).contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(voucherCreateRequestDto))) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.discountDegree").value(20)) + .andExpect(jsonPath("$.voucherPolicy").value(VoucherType.PERCENT_DISCOUNT.getDisplayName())); + + verify(voucherService).insert(any(VoucherInsertDto.class)); + } + + @Test + @DisplayName("특정 바우처를 삭제할 수 있다.") + void callDeleteVoucher() throws Exception { + UUID setUpVoucherId = UUID.randomUUID(); + + mockMvc.perform(delete(uri + "/{id}", setUpVoucherId)) + .andDo(print()) + .andExpect(status().isNoContent()); + + assertTrue(voucherService.findAll().isEmpty()); + + verify(voucherService).deleteById(setUpVoucherId); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepositoryTest.java new file mode 100644 index 0000000000..a6dfbaec2f --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/customer/CustomerJdbcRepositoryTest.java @@ -0,0 +1,138 @@ +package org.prgms.springbootbasic.repository.customer; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.exception.EntityNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("dev") +class CustomerJdbcRepositoryTest { // @Transactional은 위험할 수 있다. "A"CID. 트랜잭션은 전파 전략이 부모 레벨을 따라간다. 상위도 롤백될 가능성이. 테스트가 부모, 운영 코드가 자식. 트랜잭션 처리 공부. + // 즉, 테스트에서 @Transactional을 사용하고, 운영 코드에서도 @Transactional을 사용한다면 기본 전파 전략에 따라 테스트 @Transactional을 우선시한다. + // 하지만 이를 인지하고 잘 사용한다면 @Transactional을 사용해도 괜찮다. + + @Autowired + private CustomerRepository customerRepository; + + private Customer setUpCustomer; + + @BeforeEach + void setUp() { + setUpCustomer = new Customer(UUID.randomUUID(), + "test", + "test@gmail.com", + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + customerRepository.upsert(setUpCustomer); + } + + @AfterEach + void clean() { + customerRepository.deleteAll(); + } + + @Test + void insertNewCustomerInDB() { + Customer customer = new Customer(UUID.randomUUID(), + "test2", + "test2@gmail.com", + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + customerRepository.upsert(customer); + Optional retrievedCustomer = customerRepository.findById(customer.getCustomerId()); + + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), is(samePropertyValuesAs(customer))); + } + + @Test + void updateExistingCustomerInDB() { + Customer customer = customerRepository.findById(setUpCustomer.getCustomerId()).orElseThrow(EntityNotFoundException::new); + + Customer changedCustomer = customer.changeInfo("updated", true); + + customerRepository.upsert(changedCustomer); + + Optional retrievedCustomer = customerRepository.findById(setUpCustomer.getCustomerId()); + + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(changedCustomer)); + } + + @Test + void findCustomerByCustomerIdInDB() { + Optional retrievedCustomer = customerRepository.findById(setUpCustomer.getCustomerId()); + Optional notExistingCustomer = customerRepository.findById(UUID.randomUUID()); + + assertThat(notExistingCustomer.isPresent(), is(false)); + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(setUpCustomer)); + } + + @Test + void findCustomerByEmailInDB() { + Optional retrievedCustomer = customerRepository.findByEmail(setUpCustomer.getEmail()); + Optional notExistingCustomer = customerRepository.findByEmail("blahblah@naver.com"); + + assertThat(notExistingCustomer.isPresent(), is(false)); + assertThat(retrievedCustomer.isPresent(), is(true)); + assertThat(retrievedCustomer.get(), samePropertyValuesAs(setUpCustomer)); + } + + @Test + void findAllCustomersInDB(){ + customerRepository.upsert(new Customer(UUID.randomUUID(), "test2", "test2@gmail.com", LocalDateTime.now())); + + List customers = customerRepository.findAll(); + + assertThat(customers, hasSize(2)); + } + + @Test + void findAllBlackedCustomersInDB() { + Customer blackCustomer = new Customer(UUID.randomUUID(), + "black", + "black@gmail.com", + LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS)); + + blackCustomer.changeInfo(blackCustomer.getName(), true); + customerRepository.upsert(blackCustomer); + + List blackList = customerRepository.findBlackAll(); + + assertThat(blackList, hasSize(1)); + } + + @Test + void deleteCustomerByCustomerIdInDB() { + customerRepository.deleteById(setUpCustomer.getCustomerId()); + + var deletedCustomer = customerRepository.findById(setUpCustomer.getCustomerId()); + + assertThat(deletedCustomer.isPresent(), is(false)); + } + + @Test + void deleteAllCustomersInDB() { + customerRepository.upsert(new Customer(UUID.randomUUID(), "test2", "test2@gmail.com", LocalDateTime.now())); + + customerRepository.deleteAll(); + + List customers = customerRepository.findAll(); + + assertThat(customers, hasSize(0)); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepositoryTest.java new file mode 100644 index 0000000000..38f4df68ac --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/customervouchermanagement/CustomerVoucherManagementJdbcRepositoryTest.java @@ -0,0 +1,110 @@ +package org.prgms.springbootbasic.repository.customervouchermanagement; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.prgms.springbootbasic.repository.customer.CustomerRepository; +import org.prgms.springbootbasic.repository.voucher.VoucherRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("dev") +class CustomerVoucherManagementJdbcRepositoryTest { + @Autowired + private CustomerVoucherManagementRepository managementRepository; + @Autowired + private CustomerRepository customerRepository; + @Autowired + private VoucherRepository voucherRepository; + + private UUID setUpCustomerId; + private UUID setUpVoucherId; + + @BeforeEach + void setUp() { + setUpCustomerId = UUID.randomUUID(); + setUpVoucherId = UUID.randomUUID(); + customerRepository.upsert(new Customer(setUpCustomerId, "name", "email", LocalDateTime.now())); + voucherRepository.upsert(new Voucher(setUpVoucherId, 1000, new FixedAmountPolicy())); + } + + @AfterEach + void clean() { + managementRepository.deleteAll(); + customerRepository.deleteAll(); + voucherRepository.deleteAll(); + } + + @Test + @DisplayName("고객과 바우처가 제대로 매핑되는지 확인") + void allocateVoucherToCustomerById() { + UUID anotherVoucherId = UUID.randomUUID(); + voucherRepository.upsert(new Voucher(anotherVoucherId, 20, new PercentDiscountPolicy())); + + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + managementRepository.allocateVoucherById(setUpCustomerId, anotherVoucherId); + + List customers = managementRepository.searchCustomersByVoucherId(setUpVoucherId); + List vouchers = managementRepository.searchVouchersByCustomerId(setUpCustomerId); + + assertThat(customers, hasSize(1)); + assertThat(vouchers, hasSize(2)); + } + + @Test + @DisplayName("특정 고객과 특정 바우처 간 관계 제거") + void deleteRelationBetweenCustomerAndVoucherById() { + UUID anotherVoucherId = UUID.randomUUID(); + voucherRepository.upsert(new Voucher(anotherVoucherId, 100, new FixedAmountPolicy())); + + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + managementRepository.allocateVoucherById(setUpCustomerId, anotherVoucherId); + managementRepository.deleteVoucherById(setUpCustomerId, setUpVoucherId); + + List vouchers = managementRepository.searchVouchersByCustomerId(setUpCustomerId); + List customers = managementRepository.searchCustomersByVoucherId(setUpVoucherId); + + assertThat(vouchers, hasSize(1)); + assertThat(customers, hasSize(0)); + } + + @Test + @DisplayName("고객 id를 이용해 바우처들을 조회") + void searchVouchersRelatedToACustomerByCustomerId() { + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + + List vouchers = managementRepository.searchVouchersByCustomerId(setUpCustomerId); + List noVouchers = managementRepository.searchVouchersByCustomerId(UUID.randomUUID()); + + assertThat(vouchers, hasSize(1)); + assertThat(vouchers.get(0).getVoucherId(), is(setUpVoucherId)); + assertThat(noVouchers, hasSize(0)); + } + + @Test + @DisplayName("바우처 id를 통해 연관 고객들을 조회") + void searchCustomersRelatedToAVoucherByVoucherId() { + managementRepository.allocateVoucherById(setUpCustomerId, setUpVoucherId); + + List customers = managementRepository.searchCustomersByVoucherId(setUpVoucherId); + List noCustomers = managementRepository.searchCustomersByVoucherId(UUID.randomUUID()); + + assertThat(customers, hasSize(1)); + assertThat(customers.get(0).getCustomerId(), is(setUpCustomerId)); + assertThat(noCustomers, hasSize(0)); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepositoryTest.java new file mode 100644 index 0000000000..93406f8dba --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherCsvFileRepositoryTest.java @@ -0,0 +1,106 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@ActiveProfiles("test") +class VoucherCsvFileRepositoryTest { + + @Autowired + private VoucherCsvFileRepository voucherCsvFileRepository; + + @BeforeEach + void cleanUp(){ + voucherCsvFileRepository.deleteAll(); + } + + @Test + void findVoucherByIdFromFile() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + + Optional retrievedVoucher = voucherCsvFileRepository.findById(fixedVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), fixedVoucher); + } + + @Test + void findAllVoucherFromFile() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + Voucher percentVoucher = new Voucher(UUID.randomUUID(), 10, new PercentDiscountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + voucherCsvFileRepository.upsert(percentVoucher); + + List vouchers = voucherCsvFileRepository.findAll(); + List voucherUUIDs = vouchers.stream().map(Voucher::getVoucherId).toList(); + + assertThat(vouchers, hasSize(2)); + assertThat(voucherUUIDs, hasItem(fixedVoucher.getVoucherId())); + assertThat(voucherUUIDs, hasItem(percentVoucher.getVoucherId())); + } + + @Test + void createVoucherToFile() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 2000, new FixedAmountPolicy()); + Voucher percentVoucher = new Voucher(UUID.randomUUID(), 20, new PercentDiscountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + voucherCsvFileRepository.upsert(percentVoucher); + + List vouchers = voucherCsvFileRepository.findAll(); + List voucherUUIDs = vouchers.stream().map(Voucher::getVoucherId).toList(); + + assertThat(vouchers, hasSize(2)); + assertThat(voucherUUIDs, hasItem(fixedVoucher.getVoucherId())); + assertThat(voucherUUIDs, hasItem(percentVoucher.getVoucherId())); + assertThat(vouchers, + not(hasItem(samePropertyValuesAs(new Voucher(UUID.randomUUID(), 2000, new FixedAmountPolicy()))))); + } + + @Test + void deleteVoucherById() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + + voucherCsvFileRepository.upsert(fixedVoucher); + + assertThat(voucherCsvFileRepository.findById(fixedVoucher.getVoucherId()).isPresent(), is(true)); + + voucherCsvFileRepository.deleteById(UUID.randomUUID()); + assertThat(voucherCsvFileRepository.findAll(), hasSize(1)); + + voucherCsvFileRepository.deleteById(fixedVoucher.getVoucherId()); + assertThat(voucherCsvFileRepository.findAll(), hasSize(0)); + } + + @Test + void deleteAllVoucher() { + voucherCsvFileRepository.upsert(new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy())); + + voucherCsvFileRepository.deleteAll(); + + assertThat(voucherCsvFileRepository.findAll(), hasSize(0)); + } + + private void compareVoucher(Voucher v1, Voucher v2){ + assertThat(v1.getVoucherId(), is(v2.getVoucherId())); + assertThat(v1.getDiscountDegree(), is(v2.getDiscountDegree())); + assertThat(v1.getVoucherPolicy().getClass().getSimpleName(), is(v2.getVoucherPolicy().getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepositoryTest.java new file mode 100644 index 0000000000..3463057883 --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherJdbcRepositoryTest.java @@ -0,0 +1,111 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + + +@SpringBootTest +@ActiveProfiles("dev") +class VoucherJdbcRepositoryTest { + @Autowired + private VoucherJdbcRepository voucherJdbcRepository; + + private Voucher setUpVoucher; + @BeforeEach + void setUp() { + setUpVoucher = new Voucher(UUID.randomUUID(), 40, new PercentDiscountPolicy()); + voucherJdbcRepository.upsert(setUpVoucher); + } + + @AfterEach + void clean() { + voucherJdbcRepository.deleteAll(); + } + + @Test + void saveNewVoucherToDB() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(fixedVoucher); + + Optional retrievedVoucher = voucherJdbcRepository.findById(fixedVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), fixedVoucher); + } + + @Test + void updateVoucherInDB() { + Voucher updateVoucher = new Voucher(setUpVoucher.getVoucherId(), 2000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(updateVoucher); + + Optional retrievedVoucher = voucherJdbcRepository.findById(setUpVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), updateVoucher); + } + + @Test + void findVoucherByIdInDB() { + Optional retrievedVoucher = voucherJdbcRepository.findById(setUpVoucher.getVoucherId()); + + assertThat(retrievedVoucher.isPresent(), is(true)); + compareVoucher(retrievedVoucher.get(), setUpVoucher); + } + + @Test + void findAllVouchersInDB() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 3000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(fixedVoucher); + + List vouchers = voucherJdbcRepository.findAll(); + List voucherUUIDs = vouchers.stream().map(Voucher::getVoucherId).toList(); + + assertThat(vouchers, hasSize(2)); + assertThat(voucherUUIDs, hasItem(fixedVoucher.getVoucherId())); + assertThat(voucherUUIDs, hasItem(setUpVoucher.getVoucherId())); + } + + @Test + void deleteVoucherByIdInDB() { + voucherJdbcRepository.deleteById(setUpVoucher.getVoucherId()); + + List vouchers = voucherJdbcRepository.findAll(); + + assertThat(vouchers, hasSize(0)); + } + + @Test + void deleteAllVouchersInDB() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 3000, new FixedAmountPolicy()); + + voucherJdbcRepository.upsert(fixedVoucher); + voucherJdbcRepository.deleteAll(); + + List vouchers = voucherJdbcRepository.findAll(); + + assertThat(vouchers.size(), is(0)); + } + + private void compareVoucher(Voucher v1, Voucher v2){ + assertThat(v1.getVoucherId(), is(v2.getVoucherId())); + assertThat(v1.getDiscountDegree(), is(v2.getDiscountDegree())); + assertThat(v1.getVoucherPolicy().getClass().getSimpleName(), is(v2.getVoucherPolicy().getClass().getSimpleName())); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepositoryTest.java b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepositoryTest.java new file mode 100644 index 0000000000..1845a29fdc --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/repository/voucher/VoucherMemoryRepositoryTest.java @@ -0,0 +1,82 @@ +package org.prgms.springbootbasic.repository.voucher; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.voucher.FixedAmountPolicy; +import org.prgms.springbootbasic.domain.voucher.PercentDiscountPolicy; +import org.prgms.springbootbasic.domain.voucher.Voucher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import java.util.Optional; +import java.util.UUID; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@SpringJUnitConfig(VoucherMemoryRepository.class) +@ActiveProfiles("local") +class VoucherMemoryRepositoryTest { + + @Autowired + private VoucherMemoryRepository voucherMemoryRepository; + + @BeforeEach + void cleanUp(){ + voucherMemoryRepository.deleteAll(); + } + + @Test + void findVoucherByIdFromMemory() { + UUID voucherId = UUID.randomUUID(); + Voucher fixedVoucher = new Voucher(voucherId, 1000, new FixedAmountPolicy()); + + voucherMemoryRepository.upsert(fixedVoucher); + + Optional retrievedVoucher = voucherMemoryRepository.findById(voucherId); + Optional randomRetrievedVoucher = voucherMemoryRepository.findById(UUID.randomUUID()); + + assertThat(randomRetrievedVoucher.isEmpty(), is(true)); + assertThat(retrievedVoucher.isEmpty(), is(false)); + assertThat(retrievedVoucher.get(), samePropertyValuesAs(fixedVoucher)); + } + + @Test + void findAllVoucherFromMemory() { + Voucher percentDiscountVoucher = new Voucher(UUID.randomUUID(), 20, new PercentDiscountPolicy()); + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 6000, new FixedAmountPolicy()); + + voucherMemoryRepository.upsert(percentDiscountVoucher); + voucherMemoryRepository.upsert(fixedVoucher); + + assertThat(voucherMemoryRepository.findAll(), hasSize(2)); + assertThat(voucherMemoryRepository.findAll(), hasItem(samePropertyValuesAs(percentDiscountVoucher))); + assertThat(voucherMemoryRepository.findAll(), hasItem(samePropertyValuesAs(fixedVoucher))); + } + + @Test + void createNewVouchersToMemory() { + Voucher fixedVoucher = new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy()); + Voucher percentDiscountVoucher = new Voucher(UUID.randomUUID(), 30, new PercentDiscountPolicy()); + + this.voucherMemoryRepository.upsert(fixedVoucher); + this.voucherMemoryRepository.upsert(percentDiscountVoucher); + + assertThat(voucherMemoryRepository.findAll(), hasSize(2)); + assertThrows(IllegalArgumentException.class, + () -> voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 0, new PercentDiscountPolicy()))); // 여기에 이걸 넣는게 맞나? + } + + @Test + void deleteAllVouchersFromMemory() { + voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 10, new PercentDiscountPolicy())); + voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 20, new PercentDiscountPolicy())); + voucherMemoryRepository.upsert(new Voucher(UUID.randomUUID(), 1000, new FixedAmountPolicy())); + + voucherMemoryRepository.deleteAll(); + + assertThat(voucherMemoryRepository.findAll(), hasSize(0)); + } +} diff --git a/src/test/java/org/prgms/springbootbasic/service/CustomerServiceTest.java b/src/test/java/org/prgms/springbootbasic/service/CustomerServiceTest.java new file mode 100644 index 0000000000..bcbe268167 --- /dev/null +++ b/src/test/java/org/prgms/springbootbasic/service/CustomerServiceTest.java @@ -0,0 +1,112 @@ +package org.prgms.springbootbasic.service; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.prgms.springbootbasic.domain.customer.Customer; +import org.prgms.springbootbasic.repository.customer.CustomerRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest +@ActiveProfiles("dev") +class CustomerServiceTest { + @Autowired + private CustomerService customerService; + @MockBean + private CustomerRepository customerRepository; + + @Test + @DisplayName("insert가 제대로 동작하는지 확인") + void insertCustomer() { + Customer customer = new Customer(UUID.randomUUID(), "name", "email", LocalDateTime.now()); + + when(customerRepository.upsert(any())).thenReturn(customer); + + Customer createdCustomer = customerService.insert("name", "email"); + + assertThat(createdCustomer).isEqualTo(customer); + + verify(customerRepository).upsert(any()); + } + + @Test + @DisplayName("update가 제대로 동작하는지 확인") + void updateCustomer() { + UUID customerId = UUID.randomUUID(); + Customer customer = new Customer(customerId, "name", "email", LocalDateTime.now()); + Customer customer2 = new Customer(customerId, "name2", "email2", LocalDateTime.now()); + + when(customerRepository.findById(customerId)).thenReturn(Optional.of(customer)); + when(customerRepository.upsert(any())).thenReturn(customer2); + + Customer updatedCustomer = customerService.update(customerId, "name2", true); + + assertThat(updatedCustomer).isEqualTo(customer2); + + verify(customerRepository).upsert(any()); + } + + @Test + @DisplayName("findById로 제대로 고객을 가져오는지 확인") + void findById(){ + UUID customerId = UUID.randomUUID(); + Customer customer = new Customer(customerId, "name", "email", LocalDateTime.now()); + + when(customerRepository.findById(customerId)).thenReturn(Optional.of(customer)); + + Optional retrievedCustomer = customerService.findById(customerId); + Optional noCustomer = customerService.findById(UUID.randomUUID()); + + assertThat(retrievedCustomer.isPresent()).isTrue(); + assertThat(retrievedCustomer.get()).isEqualTo(customer); + assertThat(noCustomer.isPresent()).isFalse(); + } + + @Test + @DisplayName("findAll로 모든 고객을 가져오는지 확인") + void findAllCustomers() { + when(customerRepository.findAll()) + .thenReturn(List.of(new Customer(UUID.randomUUID(), "c1", "cmail1", LocalDateTime.now()), + new Customer(UUID.randomUUID(), "c2", "cmail2", LocalDateTime.now()))); + List customers = customerService.findAll(); + + assertThat(customers).hasSize(2); + + verify(customerRepository).findAll(); + } + + @Test + @DisplayName("CustomerService 내 deleteAll 동작 확인") + void deleteAllCustomers() { + this.customerService.deleteAll(); + + verify(customerRepository).deleteAll(); + } + + @Test + @DisplayName("CustomerService 내 findAllBlack 동작 확인") + void findAllBlackedCustomers() { + Customer customer = new Customer(UUID.randomUUID(), "c", "cmail", LocalDateTime.now()); + customer.changeInfo(customer.getName(), true); + + when(customerRepository.findBlackAll()).thenReturn(List.of(customer)); + + List blackCustomers = customerService.findBlackAll(); + + assertThat(blackCustomers).hasSize(1); + + verify(customerRepository).findBlackAll(); + } +}