From bb85df0df6c0ebe6275373f3c7b961c86a0205fd Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 1 Jul 2024 15:58:34 +0900 Subject: [PATCH 01/53] =?UTF-8?q?docs=20:=200=EB=8B=A8=EA=B3=84=20?= =?UTF-8?q?=EC=A4=80=EB=B9=84=20=EB=AC=B8=EC=84=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8376bdfff..3bc7e2563 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -# spring-gift-wishlist \ No newline at end of file +# spring-gift-wishlist + +# 2주차 위시 리스트 - 요청과 응답 심화 + +### step 0 + +1주차 코드 가져오기 + +1주차 PR에 대해서 잘못된 저장소에서 fork하는 바람에 리뷰를 받지 못하였습니다. + From 9ccee7ee7323edac4365a4bd9a5c6d6068b771b9 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 1 Jul 2024 15:58:53 +0900 Subject: [PATCH 02/53] =?UTF-8?q?init=20:=20=EC=9D=B4=EC=A0=84=20=EC=9E=91?= =?UTF-8?q?=EC=97=85=20=EC=BD=94=EB=93=9C=20=EB=B3=B5=EC=82=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/AdminPageController.java | 62 ++++++++++++++ .../gift/controller/ProductController.java | 79 +++++++++++++++++ .../gift/database/JdbcProductRepository.java | 84 +++++++++++++++++++ src/main/java/gift/dto/ProductDTO.java | 72 ++++++++++++++++ src/main/java/gift/model/Product.java | 51 +++++++++++ .../java/gift/service/ProductService.java | 26 ++++++ .../java/gift/service/ProductServiceImpl.java | 64 ++++++++++++++ 7 files changed, 438 insertions(+) create mode 100644 src/main/java/gift/controller/AdminPageController.java create mode 100644 src/main/java/gift/controller/ProductController.java create mode 100644 src/main/java/gift/database/JdbcProductRepository.java create mode 100644 src/main/java/gift/dto/ProductDTO.java create mode 100644 src/main/java/gift/model/Product.java create mode 100644 src/main/java/gift/service/ProductService.java create mode 100644 src/main/java/gift/service/ProductServiceImpl.java diff --git a/src/main/java/gift/controller/AdminPageController.java b/src/main/java/gift/controller/AdminPageController.java new file mode 100644 index 000000000..7efe83bf7 --- /dev/null +++ b/src/main/java/gift/controller/AdminPageController.java @@ -0,0 +1,62 @@ +package gift.controller; + +import gift.dto.ProductDTO; +import gift.service.ProductService; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class AdminPageController { + + private final ProductService pm; + + public AdminPageController(ProductService pm) { + this.pm = pm; + } + + @GetMapping("/admin") + public String adminPage(Model model) { + model.addAttribute("products",pm.readAll()); + model.addAttribute("productDTO",new ProductDTO()); + return "admin/index";//렌더링하는 html 이름 + } + + @PostMapping("/admin") //admin으로 오는 post에 대해서 submit + public String adminPageSubmit(@ModelAttribute("productDTO") ProductDTO productDTO) { + pm.create(productDTO); //서비스에 접근해서 해당 부분을 추가해주도록 한다. + return "redirect:/admin"; + } + + @PutMapping("/admin/{id}") + public String adminPageUpdate(@PathVariable Long id,@ModelAttribute("productDTO") ProductDTO productDTO) { + changeCheckAndUpdate(id,productDTO); + return "redirect:/admin"; + } + + @DeleteMapping("/admin/{id}") + public String adminPageDelete(@PathVariable Long id) { + pm.delete(id); + return "redirect:/admin"; + } + + private void changeCheckAndUpdate(Long id, ProductDTO dto) { + + if (dto.getName().length()>0){ + pm.updateName(id, dto.getName()); + } + if (dto.getPrice()!=null){ + pm.updatePrice(id, dto.getPrice()); + } + if (dto.getImageUrl().length()>0){ + pm.updateImageUrl(id, dto.getImageUrl()); + } + } +} diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java new file mode 100644 index 000000000..7b8a3de37 --- /dev/null +++ b/src/main/java/gift/controller/ProductController.java @@ -0,0 +1,79 @@ +package gift.controller; + +import gift.dto.ProductDTO; +import gift.service.ProductService; +import java.util.List; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 상품 추가,수정,삭제,조회를 위한 api end-point + *

+ * $/api/products + */ +@RestController +public class ProductController { + + private final ProductService pm; + + public ProductController(ProductService pm) { + this.pm = pm; + } + + /** + * 상품 전체 목록 반환 + * @return 상품 DTO + */ + @GetMapping("/api/products") + public List getList(){ + List dto = pm.readAll(); + return dto; + } + + /** + * 새로운 상품 생성 + * @param dto id가 존재하는 상태로 입력되더라도 무시됨. + */ + @PostMapping("/api/products") + public void add(ProductDTO dto){ + pm.create(dto); + } + + /** + * 기존 상품 수정 + * @param id 수정하고자 하는 상품의 id + * @param dto 수정하고자 하는 값 이외 null로 지정 + */ + @PutMapping("/api/products") + public void update(@RequestParam("id") Long id, @RequestBody ProductDTO dto){ + if(id==null){ + throw new IllegalArgumentException("id를 입력해주세요"); + } + changeCheckAndUpdate(id, dto); + } + + @DeleteMapping("/api/products") + public void delete(@RequestParam("id") Long id){ + pm.delete(id); + } + + private void changeCheckAndUpdate(Long id, ProductDTO dto) { + if (dto.getName()!=null){ + pm.updateName(id, dto.getName()); + } + if (dto.getPrice()!=null){ + pm.updatePrice(id, dto.getPrice()); + } + if (dto.getImageUrl()!=null){ + pm.updateImageUrl(id, dto.getImageUrl()); + } + } + + +} \ No newline at end of file diff --git a/src/main/java/gift/database/JdbcProductRepository.java b/src/main/java/gift/database/JdbcProductRepository.java new file mode 100644 index 000000000..10db1ac7b --- /dev/null +++ b/src/main/java/gift/database/JdbcProductRepository.java @@ -0,0 +1,84 @@ +package gift.database; + + +import gift.model.Product; +import java.sql.PreparedStatement; +import java.util.List; +import javax.sql.DataSource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcProductRepository { + private final JdbcTemplate template; + + public JdbcProductRepository(DataSource dataSource) { + this.template = new JdbcTemplate(dataSource); + createTable(); //초기 테이블 생성 + } + + private void createTable() { + template.update("create table if not exists product(" + + "id long primary key auto_increment, " + + "name varchar(255), " + + "price int," + + "imageUrl varchar(255))"); + } + + /** + * 새로운 상품 추가 + * @param product id 값은 무시됨. + * @return DB에 저장된 id값이 포함된 객체 반환 + */ + public Product insertProduct(Product product) { + String sql = "insert into product (name, price, imageUrl) values (?,?,?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + template.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql,new String[]{"id"}); + ps.setString(1, product.getName()); + ps.setInt(2, product.getPrice()); + ps.setString(3, product.getImageUrl()); + return ps; + },keyHolder); + long key = keyHolder.getKey().longValue(); + product.setId(key); + return product; + } + + //기존 상품 수정 + public void updateProduct(Long id,Product product) { + String sql = "update product set name = ?, price = ?, imageUrl = ? where id = ?"; + template.update(sql,product.getName(),product.getPrice(),product.getImageUrl(),id); + } + + //상품 단일 조회 + public Product getProduct(Long id) { + String sql = "select * from product where id = ?"; + return template.queryForObject(sql,productRowMapper(),id); + } + + //상품 전체 조회 + public List findAllProducts() { + String sql = "select * from product"; + return template.query(sql,productRowMapper()); + } + + //상품 삭제 + public void deleteProduct(Long id) { + String sql = "delete from product where id = ?"; + template.update(sql,id); + } + + + private RowMapper productRowMapper() { + return (rs, rowNum) -> new Product(rs.getLong("id"), + rs.getString("name"), + rs.getInt("price"), + rs.getString("imageUrl")); + + } + +} diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java new file mode 100644 index 000000000..12b4a8dbc --- /dev/null +++ b/src/main/java/gift/dto/ProductDTO.java @@ -0,0 +1,72 @@ +package gift.dto; + +import gift.model.Product; + +public class ProductDTO { + + private Long id; + private String name; + private Integer price; + private String imageUrl; + + //타임리프 사용을 위한 기본 생성자 + public ProductDTO() {} + + public ProductDTO(Long id, String name, Integer price, String imageUrl) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + //상품 모델을 DTO로 빠르게 전환 + public ProductDTO(Product product) { + this.id = product.getId(); + this.name = product.getName(); + this.price = product.getPrice(); + this.imageUrl = product.getImageUrl(); + } + + //디버깅 필요 시 체크용 toString + @Override + public String toString() { + return "ProductDTO{" + + "id=" + id + + ", name='" + name + '\'' + + ", price=" + price + + ", imageUrl='" + imageUrl + '\'' + + '}'; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setId(Long id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setPrice(Integer price) { + this.price = price; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} diff --git a/src/main/java/gift/model/Product.java b/src/main/java/gift/model/Product.java new file mode 100644 index 000000000..03cb9f7f1 --- /dev/null +++ b/src/main/java/gift/model/Product.java @@ -0,0 +1,51 @@ +package gift.model; + +public class Product { + + //null을 위해 wrapper class 사용 + private Long id; + private String name; + private Integer price; + private String imageUrl; + + public Product(Long id, String name, Integer price, String imageUrl) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + //setter - 타임리프 사용을 위해 필요 + public void setId(Long id) { + this.id=id; + } + + public void setName(String name) { + this.name = name; + } + + public void setPrice(int price) { + this.price = price; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + //getter + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public int getPrice() { + return price; + } + + public String getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java new file mode 100644 index 000000000..68c5f3456 --- /dev/null +++ b/src/main/java/gift/service/ProductService.java @@ -0,0 +1,26 @@ +package gift.service; + +import gift.dto.ProductDTO; +import java.util.List; + +public interface ProductService { + + /* + 상품 수정 시 만약 문제가 생기면 어떤 값에 의해서 생기는 지 파악의 어려움을 막기 위해 + update를 각 항목마다 분리 + */ + + //상품 리스트 전체 조회 + List readAll(); + //새 상품 생성 + void create(ProductDTO prod); + //상품 이름 변경 + void updateName(long id,String name); + //상품 가격 변경 + void updatePrice(long id,int price); + //상품 이미지 변경 + void updateImageUrl(long id,String url); + //상품 삭제 + void delete(long id); + +} diff --git a/src/main/java/gift/service/ProductServiceImpl.java b/src/main/java/gift/service/ProductServiceImpl.java new file mode 100644 index 000000000..393bb4d14 --- /dev/null +++ b/src/main/java/gift/service/ProductServiceImpl.java @@ -0,0 +1,64 @@ +package gift.service; + +import gift.database.JdbcProductRepository; +import gift.dto.ProductDTO; +import gift.model.Product; +import java.util.ArrayList; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class ProductServiceImpl implements ProductService{ + + private final JdbcProductRepository jdbcProductRepository; + + public ProductServiceImpl(JdbcProductRepository jdbcProductRepository) { + this.jdbcProductRepository = jdbcProductRepository; + } + + @Override + public List readAll() { + var products = jdbcProductRepository.findAllProducts(); + List productDTOList = new ArrayList<>(); + + for(var product : products) { //DTO로 전환 + productDTOList.add(new ProductDTO(product)); + } + + return productDTOList; + } + + //새로운 상품 추가 + @Override + public void create(ProductDTO prod) { + jdbcProductRepository.insertProduct(new Product(null,prod.getName(),prod.getPrice(),prod.getImageUrl())); + } + + + @Override + public void updateName(long id, String name) { + var prod = jdbcProductRepository.getProduct(id); + prod.setName(name); + jdbcProductRepository.updateProduct(id,prod); + + } + + @Override + public void updatePrice(long id, int price) { + var prod = jdbcProductRepository.getProduct(id); + prod.setPrice(price); + jdbcProductRepository.updateProduct(id,prod); + } + + @Override + public void updateImageUrl(long id, String url) { + var prod = jdbcProductRepository.getProduct(id); + prod.setImageUrl(url); + jdbcProductRepository.updateProduct(id,prod); + } + + @Override + public void delete(long id) { + jdbcProductRepository.deleteProduct(id); + } +} From 2edaa1f466ac1f51326ab0b5262491a480a328d4 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Wed, 3 Jul 2024 15:28:40 +0900 Subject: [PATCH 03/53] =?UTF-8?q?docs=20:=20step1=20milestone=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 +++++- src/main/resources/templates/admin/index.html | 66 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/templates/admin/index.html diff --git a/README.md b/README.md index 3bc7e2563..cb6e626f1 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,26 @@ # 2주차 위시 리스트 - 요청과 응답 심화 -### step 0 +# step 0 1주차 코드 가져오기 1주차 PR에 대해서 잘못된 저장소에서 fork하는 바람에 리뷰를 받지 못하였습니다. +# step 1 + +목표 + +- 상품 추가, 수정 시 잘못된 값에 대한 처리 + 응답 설정 +- 상품 이름 제한 : 공백 포함 최대 15글자 +- 일부 특수 문자만 허용 +- "카카오" 포함 문구 입력 시 따로 confirm 이후 진행 가능하도록 + +## milestone + +-[ ] 스프링 validation 의존성 추가 +-[ ] feat : DTO valid 추가 +-[ ] refact : service - 상품 update 로직 변경 (하나로 통합) +-[ ] feat : @ControllerAdvice 클래스 추가 +-[ ] feat : "카카오" 검사를 위한 예외클래스 추가 + diff --git a/src/main/resources/templates/admin/index.html b/src/main/resources/templates/admin/index.html new file mode 100644 index 000000000..47c7a64a3 --- /dev/null +++ b/src/main/resources/templates/admin/index.html @@ -0,0 +1,66 @@ + + + + + 상품 관리자 페이지 + + +

gift 상품 주문 관리자 페이지

+ + + + + + + + + + + + + +
아이디 상품명 가격 이미지 +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ +
+
+ + + +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ + + + + \ No newline at end of file From 963dd376f0717fc5e7ac216d311fa49c2a1c850e Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Wed, 3 Jul 2024 15:30:51 +0900 Subject: [PATCH 04/53] =?UTF-8?q?build=20:=20spring=20validation=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- build.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb6e626f1..5df55c753 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ ## milestone --[ ] 스프링 validation 의존성 추가 +-[X] 스프링 validation 의존성 추가 -[ ] feat : DTO valid 추가 -[ ] refact : service - 상품 update 로직 변경 (하나로 통합) -[ ] feat : @ControllerAdvice 클래스 추가 diff --git a/build.gradle b/build.gradle index df7db9334..333a9b39a 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,7 @@ dependencies { 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.boot:spring-boot-starter-validation' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From d79909ed426d643ebe8d5617785717aa66e1a348 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Wed, 3 Jul 2024 18:41:11 +0900 Subject: [PATCH 05/53] =?UTF-8?q?build=20:=20spring=20webflux=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test를 위해서 추가 --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 333a9b39a..f5f54ac12 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + testImplementation 'io.projectreactor:reactor-test' runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' From 4dc8e451c736f8b56269190f6a55c8034ee0df6f Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Wed, 3 Jul 2024 19:56:09 +0900 Subject: [PATCH 06/53] =?UTF-8?q?feat=20:=20NotNull,=20namesize=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 컨트롤러에서 해당 부분에 대해서 @Valid 구현 필요 --- src/main/java/gift/dto/ProductDTO.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java index 12b4a8dbc..5b53a726e 100644 --- a/src/main/java/gift/dto/ProductDTO.java +++ b/src/main/java/gift/dto/ProductDTO.java @@ -1,12 +1,19 @@ package gift.dto; import gift.model.Product; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + public class ProductDTO { private Long id; + @Size(min=1,max=15) + @NotNull private String name; + @NotNull private Integer price; + @NotNull private String imageUrl; //타임리프 사용을 위한 기본 생성자 From 1ce8f4b53dfb34eaa130b051e5f5d458a95094bd Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Wed, 3 Jul 2024 20:22:32 +0900 Subject: [PATCH 07/53] =?UTF-8?q?fix=20:=20add=20=EC=97=90=EC=84=9C=20@Req?= =?UTF-8?q?uestBody=20=EA=B0=80=20=EC=97=86=EC=96=B4=EC=84=9C=20=EC=83=9D?= =?UTF-8?q?=EA=B8=B0=EB=8A=94=20500=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/controller/ProductController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index 7b8a3de37..f9d62ead8 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -41,7 +41,7 @@ public List getList(){ * @param dto id가 존재하는 상태로 입력되더라도 무시됨. */ @PostMapping("/api/products") - public void add(ProductDTO dto){ + public void add(@RequestBody ProductDTO dto){ pm.create(dto); } From a98e47dd92958c2b143555e0198e3d6922a58c06 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 15:42:48 +0900 Subject: [PATCH 08/53] =?UTF-8?q?docs=20:=20=EC=9E=91=EC=97=85=20=EC=9D=BC?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- history.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 history.md diff --git a/history.md b/history.md new file mode 100644 index 000000000..a7d26a27a --- /dev/null +++ b/history.md @@ -0,0 +1,16 @@ +# 작업 일지 + +스스로 기록용도로 작성 + +## 7월 4일 (목) + +### PR 코멘트 반영하기 + +1. GetMapping 등에서 url 이 동일한 것에 대해서 통합표기 (@controller) +2. rest 도메인과 관련해서 엔드포인트 네이밍의 해석의 모호함 +3. 문법 띄워쓰기 관련 google java style guide 참고하기 (내가 못하면 cmd + option + L) 을 매번 눌러주자 +4. pm 변수명 삭제 → productService로 변경 (처음에는 productManager 정도로 생각하고 임시로 작성하였다가 바꾸지 않았던 것이 문제가 됨) +5. 불필요한 주석 삭제 → 사용하지 않는 것들에 대해서 (가독성 떨어뜨림) + +에 대해서 진행 + From 6bfa3e5ccbbe95117c22fa5ad5da1374de1663b9 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 15:43:23 +0900 Subject: [PATCH 09/53] =?UTF-8?q?refact=20:=20api=20request=EB=A5=BC=20?= =?UTF-8?q?=EC=9C=84=ED=95=9C=20=EA=B3=B5=ED=86=B5=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/ProductController.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index f9d62ead8..676a5003c 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -17,6 +18,7 @@ *

* $/api/products */ +@RequestMapping("/api") @RestController public class ProductController { @@ -28,49 +30,52 @@ public ProductController(ProductService pm) { /** * 상품 전체 목록 반환 + * * @return 상품 DTO */ - @GetMapping("/api/products") - public List getList(){ + @GetMapping("/products") + public List getList() { List dto = pm.readAll(); return dto; } /** * 새로운 상품 생성 + * * @param dto id가 존재하는 상태로 입력되더라도 무시됨. */ - @PostMapping("/api/products") - public void add(@RequestBody ProductDTO dto){ + @PostMapping("/products") + public void add(@RequestBody ProductDTO dto) { pm.create(dto); } /** * 기존 상품 수정 - * @param id 수정하고자 하는 상품의 id + * + * @param id 수정하고자 하는 상품의 id * @param dto 수정하고자 하는 값 이외 null로 지정 */ - @PutMapping("/api/products") - public void update(@RequestParam("id") Long id, @RequestBody ProductDTO dto){ - if(id==null){ + @PutMapping("/products") + public void update(@RequestParam("id") Long id, @RequestBody ProductDTO dto) { + if (id == null) { throw new IllegalArgumentException("id를 입력해주세요"); } changeCheckAndUpdate(id, dto); } - @DeleteMapping("/api/products") - public void delete(@RequestParam("id") Long id){ + @DeleteMapping("/products") + public void delete(@RequestParam("id") Long id) { pm.delete(id); } private void changeCheckAndUpdate(Long id, ProductDTO dto) { - if (dto.getName()!=null){ + if (dto.getName() != null) { pm.updateName(id, dto.getName()); } - if (dto.getPrice()!=null){ + if (dto.getPrice() != null) { pm.updatePrice(id, dto.getPrice()); } - if (dto.getImageUrl()!=null){ + if (dto.getImageUrl() != null) { pm.updateImageUrl(id, dto.getImageUrl()); } } From 8c1cf2da22737eecf04be3de9a31964f204a2ff9 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 15:44:35 +0900 Subject: [PATCH 10/53] =?UTF-8?q?test=20:=20api=20200=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 경로 변경한 것에 대해서 정상 동작 확인 --- .../controller/ProductControllerTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/gift/controller/ProductControllerTest.java diff --git a/src/test/java/gift/controller/ProductControllerTest.java b/src/test/java/gift/controller/ProductControllerTest.java new file mode 100644 index 000000000..6f316cc18 --- /dev/null +++ b/src/test/java/gift/controller/ProductControllerTest.java @@ -0,0 +1,67 @@ +package gift.controller; + +import gift.dto.ProductDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; +import org.springframework.web.reactive.function.BodyInserters; + + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ProductControllerTest { + + @LocalServerPort + private int port; + private String baseUrl; + @Autowired + private WebTestClient webClient; + + @BeforeEach + void setUp() { + baseUrl = "http://localhost:" + port; + webClient = WebTestClient.bindToServer().baseUrl(baseUrl).build(); + } + + //사실상 get test는 필요없다고 생각되어짐. + //post나 put 등의 작업에서 잘못된 데이터에 대해서 해당 응답을 돌려주는 지를 검증해야함. + @Test + @DisplayName("정상 get 응답 확인") + void getProduct() { + + webClient.get().uri("/api/products").accept(MediaType.APPLICATION_JSON).exchange() + .expectStatus().isOk(); + + } + + + @Test + @DisplayName("name이 15글자가 넘는 경우") + void addMore15word() { + //given + String name = "123123123123123123123"; + int price = 123; + String imageUrl = "abcd"; + ProductDTO dto = getProductDTO(name, price, imageUrl); + + //when then + createPostReqeust(dto).expectStatus().isBadRequest(); + } + + + private ResponseSpec createPostReqeust(ProductDTO dto) { + return webClient.post().uri("/api/products").accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(dto)).exchange(); + + //request body 에는 BodyInserters.formValue로 객체 -> body 데이터로 변환 + } + + private static ProductDTO getProductDTO(String name, int price, String imageUrl) { + return new ProductDTO(null, name, price, imageUrl); + } +} \ No newline at end of file From 1ef4e4203c1eb51417757e81cb4f4d6ab41b0ea5 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 15:47:08 +0900 Subject: [PATCH 11/53] =?UTF-8?q?refact=20:=20=EB=AA=A8=ED=98=B8=ED=95=9C?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=20=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- history.md | 4 ++-- .../gift/controller/ProductController.java | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/history.md b/history.md index a7d26a27a..d50fa10c1 100644 --- a/history.md +++ b/history.md @@ -6,10 +6,10 @@ ### PR 코멘트 반영하기 -1. GetMapping 등에서 url 이 동일한 것에 대해서 통합표기 (@controller) +1. ~~GetMapping 등에서 url 이 동일한 것에 대해서 통합표기 (@controller)~~ 2. rest 도메인과 관련해서 엔드포인트 네이밍의 해석의 모호함 3. 문법 띄워쓰기 관련 google java style guide 참고하기 (내가 못하면 cmd + option + L) 을 매번 눌러주자 -4. pm 변수명 삭제 → productService로 변경 (처음에는 productManager 정도로 생각하고 임시로 작성하였다가 바꾸지 않았던 것이 문제가 됨) +4. ~~pm 변수명 삭제 → productService로 변경 (처음에는 productManager 정도로 생각하고 임시로 작성하였다가 바꾸지 않았던 것이 문제가 됨)~~ 5. 불필요한 주석 삭제 → 사용하지 않는 것들에 대해서 (가독성 떨어뜨림) 에 대해서 진행 diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index 676a5003c..32c2fc934 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -3,7 +3,6 @@ import gift.dto.ProductDTO; import gift.service.ProductService; import java.util.List; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -22,10 +21,10 @@ @RestController public class ProductController { - private final ProductService pm; + private final ProductService productService; - public ProductController(ProductService pm) { - this.pm = pm; + public ProductController(ProductService productService) { + this.productService = productService; } /** @@ -35,7 +34,7 @@ public ProductController(ProductService pm) { */ @GetMapping("/products") public List getList() { - List dto = pm.readAll(); + List dto = productService.readAll(); return dto; } @@ -46,7 +45,7 @@ public List getList() { */ @PostMapping("/products") public void add(@RequestBody ProductDTO dto) { - pm.create(dto); + productService.create(dto); } /** @@ -65,18 +64,18 @@ public void update(@RequestParam("id") Long id, @RequestBody ProductDTO dto) { @DeleteMapping("/products") public void delete(@RequestParam("id") Long id) { - pm.delete(id); + productService.delete(id); } private void changeCheckAndUpdate(Long id, ProductDTO dto) { if (dto.getName() != null) { - pm.updateName(id, dto.getName()); + productService.updateName(id, dto.getName()); } if (dto.getPrice() != null) { - pm.updatePrice(id, dto.getPrice()); + productService.updatePrice(id, dto.getPrice()); } if (dto.getImageUrl() != null) { - pm.updateImageUrl(id, dto.getImageUrl()); + productService.updateImageUrl(id, dto.getImageUrl()); } } From 59b5741a53ee1324a55a6525b39236811ca24623 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 15:57:20 +0900 Subject: [PATCH 12/53] =?UTF-8?q?refact=20:=20=EB=AA=A8=ED=98=B8=ED=95=9C?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=20=EB=AA=85=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EA=B3=B5=ED=86=B5=20=EA=B2=BD=EB=A1=9C=20=EC=A0=95=EB=A6=AC+re?= =?UTF-8?q?st=20=ED=95=98=EA=B2=8C=20=EA=B2=BD=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리자 페이지는 "/admin/products" 로 변경 --- .../gift/controller/AdminPageController.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/gift/controller/AdminPageController.java b/src/main/java/gift/controller/AdminPageController.java index 7efe83bf7..bcf83978d 100644 --- a/src/main/java/gift/controller/AdminPageController.java +++ b/src/main/java/gift/controller/AdminPageController.java @@ -10,53 +10,53 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.RequestMapping; +@RequestMapping("/admin/products") @Controller public class AdminPageController { - private final ProductService pm; + private final ProductService productService; - public AdminPageController(ProductService pm) { - this.pm = pm; + public AdminPageController(ProductService productService) { + this.productService = productService; } - @GetMapping("/admin") + @GetMapping public String adminPage(Model model) { - model.addAttribute("products",pm.readAll()); + model.addAttribute("products", productService.readAll()); model.addAttribute("productDTO",new ProductDTO()); return "admin/index";//렌더링하는 html 이름 } - @PostMapping("/admin") //admin으로 오는 post에 대해서 submit + @PostMapping //admin으로 오는 post에 대해서 submit public String adminPageSubmit(@ModelAttribute("productDTO") ProductDTO productDTO) { - pm.create(productDTO); //서비스에 접근해서 해당 부분을 추가해주도록 한다. - return "redirect:/admin"; + productService.create(productDTO); //서비스에 접근해서 해당 부분을 추가해주도록 한다. + return "redirect:/admin/products"; } - @PutMapping("/admin/{id}") + @PutMapping("/{id}") public String adminPageUpdate(@PathVariable Long id,@ModelAttribute("productDTO") ProductDTO productDTO) { changeCheckAndUpdate(id,productDTO); - return "redirect:/admin"; + return "redirect:/admin/products"; } - @DeleteMapping("/admin/{id}") + @DeleteMapping("/{id}") public String adminPageDelete(@PathVariable Long id) { - pm.delete(id); - return "redirect:/admin"; + productService.delete(id); + return "redirect:/admin/products"; } private void changeCheckAndUpdate(Long id, ProductDTO dto) { if (dto.getName().length()>0){ - pm.updateName(id, dto.getName()); + productService.updateName(id, dto.getName()); } if (dto.getPrice()!=null){ - pm.updatePrice(id, dto.getPrice()); + productService.updatePrice(id, dto.getPrice()); } if (dto.getImageUrl().length()>0){ - pm.updateImageUrl(id, dto.getImageUrl()); + productService.updateImageUrl(id, dto.getImageUrl()); } } } From c1e4ceec773462eacb0ca20271b7595737b80c93 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 18:53:55 +0900 Subject: [PATCH 13/53] =?UTF-8?q?feat=20:=20product=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EC=8B=9C=20name=20=EA=B8=B8=EC=9D=B4=20Valid=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전역적으로 예외 처리를 위한 Advisor 추가 예외관련 내용을 담은 ExceptionResponseDTO 추가 --- history.md | 4 ++++ .../gift/controller/ProductController.java | 3 ++- .../java/gift/dto/ExceptionResponseDTO.java | 14 +++++++++++ .../ProductExceptionAdvisor.java | 23 +++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/main/java/gift/dto/ExceptionResponseDTO.java create mode 100644 src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java diff --git a/history.md b/history.md index d50fa10c1..6da4f9a46 100644 --- a/history.md +++ b/history.md @@ -14,3 +14,7 @@ 에 대해서 진행 +### valid 적용 테스트 코드 작성하기 + +이름 길이가 15글자를 넘는 경우와 0글자인 경우에 대해 로직과 테스트 추가 + diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index 32c2fc934..ec3a4b16e 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -2,6 +2,7 @@ import gift.dto.ProductDTO; import gift.service.ProductService; +import jakarta.validation.Valid; import java.util.List; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -44,7 +45,7 @@ public List getList() { * @param dto id가 존재하는 상태로 입력되더라도 무시됨. */ @PostMapping("/products") - public void add(@RequestBody ProductDTO dto) { + public void add(@RequestBody @Valid ProductDTO dto) { productService.create(dto); } diff --git a/src/main/java/gift/dto/ExceptionResponseDTO.java b/src/main/java/gift/dto/ExceptionResponseDTO.java new file mode 100644 index 000000000..57af8f978 --- /dev/null +++ b/src/main/java/gift/dto/ExceptionResponseDTO.java @@ -0,0 +1,14 @@ +package gift.dto; + +public class ExceptionResponseDTO { + + private String message; + + public ExceptionResponseDTO(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java new file mode 100644 index 000000000..e46302aa8 --- /dev/null +++ b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java @@ -0,0 +1,23 @@ +package gift.exceptionAdvisor; + + +import gift.dto.ExceptionResponseDTO; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestController; + +@ControllerAdvice +@RestController +public class ProductExceptionAdvisor { + + @ExceptionHandler(MethodArgumentNotValidException.class) //유효성 검사 실패 시 + public ResponseEntity productValidationException( + MethodArgumentNotValidException ex) { + return new ResponseEntity<>( + new ExceptionResponseDTO(ex.getBindingResult().getFieldError().getDefaultMessage()), + HttpStatus.BAD_REQUEST); + } +} From 1c688634a9300cb6189d1fa608c865f4047bdc50 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 18:54:19 +0900 Subject: [PATCH 14/53] =?UTF-8?q?refact=20:=20NotNull=EA=B3=BC=20size=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/dto/ProductDTO.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java index 5b53a726e..e34f9c937 100644 --- a/src/main/java/gift/dto/ProductDTO.java +++ b/src/main/java/gift/dto/ProductDTO.java @@ -8,12 +8,11 @@ public class ProductDTO { private Long id; - @Size(min=1,max=15) - @NotNull + @Size(min = 1, max = 15, message = "상품 이름은 1~15글자로 제한됩니다.") private String name; - @NotNull + @NotNull(message = "가격을 입력해주세요") private Integer price; - @NotNull + @NotNull(message = "이미지 주소를 입력해주세요") private String imageUrl; //타임리프 사용을 위한 기본 생성자 From 9bed6ab600dbcbc7f95f7a4d1c3132bb930dfe11 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 18:54:50 +0900 Subject: [PATCH 15/53] =?UTF-8?q?feat=20:=20Product=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/ProductControllerTest.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/test/java/gift/controller/ProductControllerTest.java b/src/test/java/gift/controller/ProductControllerTest.java index 6f316cc18..48dc9db0b 100644 --- a/src/test/java/gift/controller/ProductControllerTest.java +++ b/src/test/java/gift/controller/ProductControllerTest.java @@ -41,7 +41,7 @@ void getProduct() { @Test - @DisplayName("name이 15글자가 넘는 경우") + @DisplayName("name이 15글자가 넘는 경우 실패") void addMore15word() { //given String name = "123123123123123123123"; @@ -51,9 +51,23 @@ void addMore15word() { //when then createPostReqeust(dto).expectStatus().isBadRequest(); + + } + + @Test + @DisplayName("name이 0글자인 경우 실패") + void addZero() { + //given + ProductDTO dto = getProductDTO("", 123, "test"); + + //when + createPostReqeust(dto).expectStatus().isBadRequest().expectBody() + .jsonPath("$.message").isEqualTo("상품 이름은 1~15글자로 제한됩니다."); + } + private ResponseSpec createPostReqeust(ProductDTO dto) { return webClient.post().uri("/api/products").accept(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(dto)).exchange(); From dd205dcc86dc7a9a487495aa5cc9e17bc994065d Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 19:11:37 +0900 Subject: [PATCH 16/53] =?UTF-8?q?refact=20:=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=EC=9D=B4=20=EB=B9=84=EC=96=B4=EC=9E=88=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=EB=A5=BC=20=EC=9C=84=ED=95=B4=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/dto/ProductDTO.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java index e34f9c937..7b2c4904d 100644 --- a/src/main/java/gift/dto/ProductDTO.java +++ b/src/main/java/gift/dto/ProductDTO.java @@ -1,6 +1,7 @@ package gift.dto; import gift.model.Product; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; @@ -12,7 +13,7 @@ public class ProductDTO { private String name; @NotNull(message = "가격을 입력해주세요") private Integer price; - @NotNull(message = "이미지 주소를 입력해주세요") + @NotBlank(message = "이미지 주소를 입력해주세요") private String imageUrl; //타임리프 사용을 위한 기본 생성자 From f9637f0dc7dbdec24c3fd57f0c3640cdfda48169 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 19:11:57 +0900 Subject: [PATCH 17/53] =?UTF-8?q?test=20:=20=EA=B0=80=EA=B2=A9=EA=B3=BC=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=EA=B0=80=20=EB=B9=84=EC=96=B4?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProductControllerTest.java | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/test/java/gift/controller/ProductControllerTest.java b/src/test/java/gift/controller/ProductControllerTest.java index 48dc9db0b..b88221145 100644 --- a/src/test/java/gift/controller/ProductControllerTest.java +++ b/src/test/java/gift/controller/ProductControllerTest.java @@ -40,6 +40,10 @@ void getProduct() { } + private static ProductDTO getProductDTO(String name, Integer price, String imageUrl) { + return new ProductDTO(null, name, price, imageUrl); + } + @Test @DisplayName("name이 15글자가 넘는 경우 실패") void addMore15word() { @@ -50,7 +54,7 @@ void addMore15word() { ProductDTO dto = getProductDTO(name, price, imageUrl); //when then - createPostReqeust(dto).expectStatus().isBadRequest(); + createPostAndCheckBadRequest(dto, "상품 이름은 1~15글자로 제한됩니다."); } @@ -61,12 +65,29 @@ void addZero() { ProductDTO dto = getProductDTO("", 123, "test"); //when - createPostReqeust(dto).expectStatus().isBadRequest().expectBody() - .jsonPath("$.message").isEqualTo("상품 이름은 1~15글자로 제한됩니다."); + createPostAndCheckBadRequest(dto, "상품 이름은 1~15글자로 제한됩니다."); + + } + + @Test + @DisplayName("price가 0인 경우 실패") + void priceZero() { + + ProductDTO dto = getProductDTO("asd", null, "test"); + + createPostAndCheckBadRequest(dto, "가격을 입력해주세요"); } + //private function// + @Test + @DisplayName("imgUrl 이 없는 경우 실패") + void imgUrlNotInput() { + ProductDTO dto = getProductDTO("asd", 123, null); + + createPostAndCheckBadRequest(dto, "이미지 주소를 입력해주세요"); + } private ResponseSpec createPostReqeust(ProductDTO dto) { return webClient.post().uri("/api/products").accept(MediaType.APPLICATION_JSON) @@ -75,7 +96,9 @@ private ResponseSpec createPostReqeust(ProductDTO dto) { //request body 에는 BodyInserters.formValue로 객체 -> body 데이터로 변환 } - private static ProductDTO getProductDTO(String name, int price, String imageUrl) { - return new ProductDTO(null, name, price, imageUrl); + private void createPostAndCheckBadRequest(ProductDTO dto, String compareMsg) { + createPostReqeust(dto).expectStatus().isBadRequest().expectBody().jsonPath("$.message") + .isEqualTo(compareMsg); } + } \ No newline at end of file From 8f3fa03c1aade54b47f4b12543cef4a33753bcd2 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Thu, 4 Jul 2024 23:16:14 +0900 Subject: [PATCH 18/53] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=ED=8A=B9=EC=88=98=EB=AC=B8=EC=9E=90=20=ED=83=90?= =?UTF-8?q?=EC=83=89=20Valid=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/dto/ProductDTO.java | 3 +++ .../controller/ProductControllerTest.java | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java index 7b2c4904d..dd986ab95 100644 --- a/src/main/java/gift/dto/ProductDTO.java +++ b/src/main/java/gift/dto/ProductDTO.java @@ -3,6 +3,7 @@ import gift.model.Product; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; @@ -10,6 +11,8 @@ public class ProductDTO { private Long id; @Size(min = 1, max = 15, message = "상품 이름은 1~15글자로 제한됩니다.") + @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-zA-Z0-9\\(\\)\\[\\]\\+\\-&/_]+$", message = "사용할 수 없는 특수문자입니다.") + //이 정규표현식을 만족해야지만 ok private String name; @NotNull(message = "가격을 입력해주세요") private Integer price; diff --git a/src/test/java/gift/controller/ProductControllerTest.java b/src/test/java/gift/controller/ProductControllerTest.java index b88221145..8c2b1353b 100644 --- a/src/test/java/gift/controller/ProductControllerTest.java +++ b/src/test/java/gift/controller/ProductControllerTest.java @@ -79,8 +79,6 @@ void priceZero() { } - //private function// - @Test @DisplayName("imgUrl 이 없는 경우 실패") void imgUrlNotInput() { @@ -89,6 +87,25 @@ void imgUrlNotInput() { createPostAndCheckBadRequest(dto, "이미지 주소를 입력해주세요"); } + @Test + @DisplayName("사용할 수 없는 특수문자") + void nameCantUseWords() { + ProductDTO dto = getProductDTO("asd!", 123, "test"); + ProductDTO dto2 = getProductDTO("asd?", 123, "test2"); + ProductDTO dto3 = getProductDTO("&/_+-[]()", 123, "test3"); + ProductDTO dto4 = getProductDTO("asdd", 123, "ttt"); + + createPostAndCheckBadRequest(dto, "사용할 수 없는 특수문자입니다."); + createPostAndCheckBadRequest(dto2, "사용할 수 없는 특수문자입니다."); + + createPostReqeust(dto3).expectStatus().isOk(); + createPostReqeust(dto4).expectStatus().isOk(); + } + + + //private function// + + private ResponseSpec createPostReqeust(ProductDTO dto) { return webClient.post().uri("/api/products").accept(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(dto)).exchange(); From b11dde549f527c140d50cd9a56f699ab995ba9df Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Fri, 5 Jul 2024 06:29:31 +0900 Subject: [PATCH 19/53] =?UTF-8?q?feat=20:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20Valid=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/AdminPageController.java | 16 ++++++++------ .../gift/controller/ProductController.java | 2 +- .../ProductExceptionAdvisor.java | 13 ++++++++--- .../ProductServiceException.java | 11 ++++++++++ .../java/gift/service/ProductServiceImpl.java | 22 ++++++++++++++----- .../controller/ProductControllerTest.java | 7 ++++++ 6 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 src/main/java/gift/exceptionAdvisor/ProductServiceException.java diff --git a/src/main/java/gift/controller/AdminPageController.java b/src/main/java/gift/controller/AdminPageController.java index bcf83978d..fbd52896a 100644 --- a/src/main/java/gift/controller/AdminPageController.java +++ b/src/main/java/gift/controller/AdminPageController.java @@ -2,6 +2,7 @@ import gift.dto.ProductDTO; import gift.service.ProductService; +import jakarta.validation.Valid; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.DeleteMapping; @@ -25,19 +26,20 @@ public AdminPageController(ProductService productService) { @GetMapping public String adminPage(Model model) { model.addAttribute("products", productService.readAll()); - model.addAttribute("productDTO",new ProductDTO()); + model.addAttribute("productDTO", new ProductDTO()); return "admin/index";//렌더링하는 html 이름 } @PostMapping //admin으로 오는 post에 대해서 submit - public String adminPageSubmit(@ModelAttribute("productDTO") ProductDTO productDTO) { + public String adminPageSubmit(@ModelAttribute("productDTO") @Valid ProductDTO productDTO) { productService.create(productDTO); //서비스에 접근해서 해당 부분을 추가해주도록 한다. return "redirect:/admin/products"; } @PutMapping("/{id}") - public String adminPageUpdate(@PathVariable Long id,@ModelAttribute("productDTO") ProductDTO productDTO) { - changeCheckAndUpdate(id,productDTO); + public String adminPageUpdate(@PathVariable Long id, + @ModelAttribute("productDTO") @Valid ProductDTO productDTO) { + changeCheckAndUpdate(id, productDTO); return "redirect:/admin/products"; } @@ -49,13 +51,13 @@ public String adminPageDelete(@PathVariable Long id) { private void changeCheckAndUpdate(Long id, ProductDTO dto) { - if (dto.getName().length()>0){ + if (dto.getName().length() > 0) { productService.updateName(id, dto.getName()); } - if (dto.getPrice()!=null){ + if (dto.getPrice() != null) { productService.updatePrice(id, dto.getPrice()); } - if (dto.getImageUrl().length()>0){ + if (dto.getImageUrl().length() > 0) { productService.updateImageUrl(id, dto.getImageUrl()); } } diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index ec3a4b16e..c1dc523aa 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -56,7 +56,7 @@ public void add(@RequestBody @Valid ProductDTO dto) { * @param dto 수정하고자 하는 값 이외 null로 지정 */ @PutMapping("/products") - public void update(@RequestParam("id") Long id, @RequestBody ProductDTO dto) { + public void update(@RequestParam("id") Long id, @RequestBody @Valid ProductDTO dto) { if (id == null) { throw new IllegalArgumentException("id를 입력해주세요"); } diff --git a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java index e46302aa8..19fe26ea1 100644 --- a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java +++ b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java @@ -15,9 +15,16 @@ public class ProductExceptionAdvisor { @ExceptionHandler(MethodArgumentNotValidException.class) //유효성 검사 실패 시 public ResponseEntity productValidationException( - MethodArgumentNotValidException ex) { - return new ResponseEntity<>( - new ExceptionResponseDTO(ex.getBindingResult().getFieldError().getDefaultMessage()), + MethodArgumentNotValidException exception) { + return new ResponseEntity<>(new ExceptionResponseDTO( + exception.getBindingResult().getFieldError().getDefaultMessage()), + HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(ProductServiceException.class) + public ResponseEntity productServiceException( + ProductServiceException exception) { + return new ResponseEntity<>(new ExceptionResponseDTO(exception.getMessage()), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/gift/exceptionAdvisor/ProductServiceException.java b/src/main/java/gift/exceptionAdvisor/ProductServiceException.java new file mode 100644 index 000000000..973978699 --- /dev/null +++ b/src/main/java/gift/exceptionAdvisor/ProductServiceException.java @@ -0,0 +1,11 @@ +package gift.exceptionAdvisor; + +public class ProductServiceException extends RuntimeException { + + public ProductServiceException() { + } + + public ProductServiceException(String message) { + super(message); + } +} diff --git a/src/main/java/gift/service/ProductServiceImpl.java b/src/main/java/gift/service/ProductServiceImpl.java index 393bb4d14..2747dcf19 100644 --- a/src/main/java/gift/service/ProductServiceImpl.java +++ b/src/main/java/gift/service/ProductServiceImpl.java @@ -2,13 +2,14 @@ import gift.database.JdbcProductRepository; import gift.dto.ProductDTO; +import gift.exceptionAdvisor.ProductServiceException; import gift.model.Product; import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; @Service -public class ProductServiceImpl implements ProductService{ +public class ProductServiceImpl implements ProductService { private final JdbcProductRepository jdbcProductRepository; @@ -21,7 +22,7 @@ public List readAll() { var products = jdbcProductRepository.findAllProducts(); List productDTOList = new ArrayList<>(); - for(var product : products) { //DTO로 전환 + for (var product : products) { //DTO로 전환 productDTOList.add(new ProductDTO(product)); } @@ -31,15 +32,18 @@ public List readAll() { //새로운 상품 추가 @Override public void create(ProductDTO prod) { - jdbcProductRepository.insertProduct(new Product(null,prod.getName(),prod.getPrice(),prod.getImageUrl())); + checkKakao(prod.getName()); + jdbcProductRepository.insertProduct( + new Product(null, prod.getName(), prod.getPrice(), prod.getImageUrl())); } @Override public void updateName(long id, String name) { var prod = jdbcProductRepository.getProduct(id); + checkKakao(prod.getName()); prod.setName(name); - jdbcProductRepository.updateProduct(id,prod); + jdbcProductRepository.updateProduct(id, prod); } @@ -47,18 +51,24 @@ public void updateName(long id, String name) { public void updatePrice(long id, int price) { var prod = jdbcProductRepository.getProduct(id); prod.setPrice(price); - jdbcProductRepository.updateProduct(id,prod); + jdbcProductRepository.updateProduct(id, prod); } @Override public void updateImageUrl(long id, String url) { var prod = jdbcProductRepository.getProduct(id); prod.setImageUrl(url); - jdbcProductRepository.updateProduct(id,prod); + jdbcProductRepository.updateProduct(id, prod); } @Override public void delete(long id) { jdbcProductRepository.deleteProduct(id); } + + private void checkKakao(String productName) { + if (productName.contains("카카오")) { + throw new ProductServiceException("카카오 문구는 md협의 이후 사용할 수 있습니다."); + } + } } diff --git a/src/test/java/gift/controller/ProductControllerTest.java b/src/test/java/gift/controller/ProductControllerTest.java index 8c2b1353b..0daf32819 100644 --- a/src/test/java/gift/controller/ProductControllerTest.java +++ b/src/test/java/gift/controller/ProductControllerTest.java @@ -102,6 +102,13 @@ void nameCantUseWords() { createPostReqeust(dto4).expectStatus().isOk(); } + @Test + @DisplayName("카카오 사용하기") + void useKakao() { + ProductDTO dto = getProductDTO("나카카오콩따러간다", 123, "test"); + + createPostAndCheckBadRequest(dto, "카카오 문구는 md협의 이후 사용할 수 있습니다."); + } //private function// From 0a578c4dc4a251d5f70822912ab27cea15b19ceb Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Fri, 5 Jul 2024 06:30:18 +0900 Subject: [PATCH 20/53] =?UTF-8?q?docs=20:=20=EC=9E=91=EC=97=85=EC=9D=BC?= =?UTF-8?q?=EC=A7=80=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- history.md | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5df55c753..dda1c2758 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,8 @@ ## milestone -[X] 스프링 validation 의존성 추가 --[ ] feat : DTO valid 추가 +-[X] feat : DTO valid 추가 -[ ] refact : service - 상품 update 로직 변경 (하나로 통합) --[ ] feat : @ControllerAdvice 클래스 추가 --[ ] feat : "카카오" 검사를 위한 예외클래스 추가 +-[X] feat : @ControllerAdvice 클래스 추가 +-[X] feat : "카카오" 검사를 위한 예외클래스 추가 diff --git a/history.md b/history.md index 6da4f9a46..f97340763 100644 --- a/history.md +++ b/history.md @@ -18,3 +18,9 @@ 이름 길이가 15글자를 넘는 경우와 0글자인 경우에 대해 로직과 테스트 추가 +### TODO + +예외에 대한 메세지가 너무 흩어져있어서 관리가 안되고 있다고 생각되어짐. +한 군데로 모아서 예외에 대한 관리를 진행할 필요가 있다고 생각. +예외 테스트코드에서 문자열 비교를 통해서 assert 하는 것은 너무 안좋은 방법이라고 생각되어짐. + From 8fb79979e835e3c5b4a5a83a2dfae88fbc959589 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Fri, 5 Jul 2024 06:30:58 +0900 Subject: [PATCH 21/53] =?UTF-8?q?chore=20:=20=EA=B0=80=EB=8F=85=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/dto/ExceptionResponseDTO.java | 2 -- src/main/java/gift/dto/ProductDTO.java | 21 ++++++++----------- .../java/gift/service/ProductService.java | 11 +++++++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/gift/dto/ExceptionResponseDTO.java b/src/main/java/gift/dto/ExceptionResponseDTO.java index 57af8f978..423195e6a 100644 --- a/src/main/java/gift/dto/ExceptionResponseDTO.java +++ b/src/main/java/gift/dto/ExceptionResponseDTO.java @@ -1,13 +1,11 @@ package gift.dto; public class ExceptionResponseDTO { - private String message; public ExceptionResponseDTO(String message) { this.message = message; } - public String getMessage() { return message; } diff --git a/src/main/java/gift/dto/ProductDTO.java b/src/main/java/gift/dto/ProductDTO.java index dd986ab95..e6a672c2b 100644 --- a/src/main/java/gift/dto/ProductDTO.java +++ b/src/main/java/gift/dto/ProductDTO.java @@ -9,18 +9,19 @@ public class ProductDTO { - private Long id; - @Size(min = 1, max = 15, message = "상품 이름은 1~15글자로 제한됩니다.") + private Long id; @Pattern(regexp = "^[ㄱ-ㅎ가-힣a-zA-Z0-9\\(\\)\\[\\]\\+\\-&/_]+$", message = "사용할 수 없는 특수문자입니다.") + @Size(min = 1, max = 15, message = "상품 이름은 1~15글자로 제한됩니다.") //이 정규표현식을 만족해야지만 ok - private String name; + private String name; @NotNull(message = "가격을 입력해주세요") - private Integer price; + private Integer price; @NotBlank(message = "이미지 주소를 입력해주세요") - private String imageUrl; + private String imageUrl; //타임리프 사용을 위한 기본 생성자 - public ProductDTO() {} + public ProductDTO() { + } public ProductDTO(Long id, String name, Integer price, String imageUrl) { this.id = id; @@ -40,12 +41,8 @@ public ProductDTO(Product product) { //디버깅 필요 시 체크용 toString @Override public String toString() { - return "ProductDTO{" + - "id=" + id + - ", name='" + name + '\'' + - ", price=" + price + - ", imageUrl='" + imageUrl + '\'' + - '}'; + return "ProductDTO{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + + ", imageUrl='" + imageUrl + '\'' + '}'; } public Long getId() { diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java index 68c5f3456..432c2bb7d 100644 --- a/src/main/java/gift/service/ProductService.java +++ b/src/main/java/gift/service/ProductService.java @@ -12,14 +12,19 @@ public interface ProductService { //상품 리스트 전체 조회 List readAll(); + //새 상품 생성 void create(ProductDTO prod); + //상품 이름 변경 - void updateName(long id,String name); + void updateName(long id, String name); + //상품 가격 변경 - void updatePrice(long id,int price); + void updatePrice(long id, int price); + //상품 이미지 변경 - void updateImageUrl(long id,String url); + void updateImageUrl(long id, String url); + //상품 삭제 void delete(long id); From 1f1844aaa2a8761a411008a3b06029289a051534 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Fri, 5 Jul 2024 15:29:11 +0900 Subject: [PATCH 22/53] =?UTF-8?q?docs=20:=20milestone=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit step2를 위해서 --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index dda1c2758..bc3d3cc1b 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,24 @@ -[X] feat : @ControllerAdvice 클래스 추가 -[X] feat : "카카오" 검사를 위한 예외클래스 추가 +## milestone 2 + +회원 가입, 로그인, 추후 회원별 기능 이용을 위해 + +회원은 이메일과 비밀번호를 입력하여 가입한다. +토큰을 받으려면 이메일과 비밀번호를 보내야 하며, 가입한 이메일과 비밀번호가 일치하면 토큰이 발급된다. +토큰은 JWT 를 사용하도록 한다. +관리자는 회원을 조회 추가 수정 삭제 할 수 있다. + +- [ ] feat : 회원 모델 만들기 +- [ ] feat : 회원 Repository 만들기 +- [ ] feat : 회원 가입 서비스 만들기 +- [ ] feat : 회원 가입 컨트롤러 만들기 +- [ ] feat : 로그인 서비스 만들기 +- [ ] feat : 인증 서비스 만들기 +- [ ] feat : 인증 컨트롤러 만들기 + +### 회원 모델 만들기 + +Member.class : 회원모델 +MemberRole.class : 회원 등급 enum class From a9c327bb70e4c4598402c2066577e167547df7b9 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Fri, 5 Jul 2024 15:30:05 +0900 Subject: [PATCH 23/53] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=9B=90=20=EB=AA=A8?= =?UTF-8?q?=EB=8D=B8=20+=20=ED=9A=8C=EC=9B=90=20=EB=93=B1=EA=B8=89=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- history.md | 9 +++++++ src/main/java/gift/model/Member.java | 33 ++++++++++++++++++++++++ src/main/java/gift/model/MemberRole.java | 5 ++++ 3 files changed, 47 insertions(+) create mode 100644 src/main/java/gift/model/Member.java create mode 100644 src/main/java/gift/model/MemberRole.java diff --git a/history.md b/history.md index f97340763..76ebf3189 100644 --- a/history.md +++ b/history.md @@ -24,3 +24,12 @@ 한 군데로 모아서 예외에 대한 관리를 진행할 필요가 있다고 생각. 예외 테스트코드에서 문자열 비교를 통해서 assert 하는 것은 너무 안좋은 방법이라고 생각되어짐. +## 7월 5일 (금) + +1. 회원가입 관련 구현 +2. 로그인 관련 구현 + +궁금증 : 모델에서 null 허용이 되는 wrapper class를 사용하는 것이 좋은 방법일까? 나쁜 방법일까? + +-[ ] Product 에 setter가 필요한가? +- \ No newline at end of file diff --git a/src/main/java/gift/model/Member.java b/src/main/java/gift/model/Member.java new file mode 100644 index 000000000..9adff91a7 --- /dev/null +++ b/src/main/java/gift/model/Member.java @@ -0,0 +1,33 @@ +package gift.model; + +public class Member { + + private Long id; + private String email; + private String password; + private MemberRole role; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/gift/model/MemberRole.java b/src/main/java/gift/model/MemberRole.java new file mode 100644 index 000000000..2a555e5bc --- /dev/null +++ b/src/main/java/gift/model/MemberRole.java @@ -0,0 +1,5 @@ +package gift.model; + +public enum MemberRole { + GUEST, COMMON_MEMBER, ADMIN_MEMBER +} From 9bdaa3d6c2d8a80dee3009d22d7a6f23acd09e41 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Fri, 5 Jul 2024 17:34:02 +0900 Subject: [PATCH 24/53] =?UTF-8?q?build=20:=20JWT=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index f5f54ac12..e32fc3a28 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,9 @@ dependencies { runtimeOnly 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + compileOnly 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' } tasks.named('test') { From 7ec163396fcaf116d1f36757b8807a86c0e5e544 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:00:40 +0900 Subject: [PATCH 25/53] =?UTF-8?q?feat=20:=20memberRepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/database/JdbcMemeberRepository.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/gift/database/JdbcMemeberRepository.java diff --git a/src/main/java/gift/database/JdbcMemeberRepository.java b/src/main/java/gift/database/JdbcMemeberRepository.java new file mode 100644 index 000000000..64af3f96e --- /dev/null +++ b/src/main/java/gift/database/JdbcMemeberRepository.java @@ -0,0 +1,57 @@ +package gift.database; + +import gift.model.Member; +import gift.model.MemberRole; +import java.sql.PreparedStatement; +import javax.sql.DataSource; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcMemeberRepository { + + private final JdbcTemplate template; + + public JdbcMemeberRepository(DataSource dataSource) { + this.template = new JdbcTemplate(dataSource); + createTable(); + } + + private void createTable() { + template.update( + "create table if not exists member(" + + "id long primary key auto_increment, " + + "email varchar(255) not null, " + + "password varchar(255) not null, " + + "role varchar(255) not null, " + + "token varchar(255))"); + } + + public Member findByEmail(String email) { + String sql = "select * from member where email = ?"; + return template.queryForObject(sql, memberRowMapper(), email); + } + + public void create(String email, String password, String role) { + String sql = "insert into member (email, password,role) values (?, ?, ?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + template.update(connection -> { + PreparedStatement ps = connection.prepareStatement(sql, new String[]{"id"}); + ps.setString(1, email); + ps.setString(2, password); + ps.setString(3, role); + return ps; + }, keyHolder); + } + + private RowMapper memberRowMapper() { + return (rs, rowNum) -> new Member( + rs.getLong("id"), + rs.getString("email"), + rs.getString("password"), + MemberRole.valueOf(rs.getString("role"))); + } +} From 99257350b0e327c08018ed47325d29fb4f3ec115 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:01:28 +0900 Subject: [PATCH 26/53] =?UTF-8?q?feat=20:=20memberService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/dto/LoginToken.java | 48 ++++++++++++++ src/main/java/gift/dto/MemberDTO.java | 52 ++++++++++++++++ .../MemberServiceException.java | 21 +++++++ src/main/java/gift/model/Member.java | 15 +++++ src/main/java/gift/service/MemberService.java | 17 +++++ .../java/gift/service/MemberServiceImpl.java | 62 +++++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 src/main/java/gift/dto/LoginToken.java create mode 100644 src/main/java/gift/dto/MemberDTO.java create mode 100644 src/main/java/gift/exceptionAdvisor/MemberServiceException.java create mode 100644 src/main/java/gift/service/MemberService.java create mode 100644 src/main/java/gift/service/MemberServiceImpl.java diff --git a/src/main/java/gift/dto/LoginToken.java b/src/main/java/gift/dto/LoginToken.java new file mode 100644 index 000000000..5e10d0557 --- /dev/null +++ b/src/main/java/gift/dto/LoginToken.java @@ -0,0 +1,48 @@ +package gift.dto; + +import gift.model.MemberRole; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +public class LoginToken { + + private String token; + private String secretKey = "testword"; + + public LoginToken() { + } + + public LoginToken(String email, MemberRole role) { + this.token = Jwts.builder() + .setSubject(email) + .claim("role", role.toString()) + .signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) + .compact(); + } + + public String getToken() { + return token; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LoginToken that = (LoginToken) o; + return Objects.equals(token, that.token); + } + + @Override + public int hashCode() { + return Objects.hashCode(token); + } + + +} diff --git a/src/main/java/gift/dto/MemberDTO.java b/src/main/java/gift/dto/MemberDTO.java new file mode 100644 index 000000000..3f83e6e6f --- /dev/null +++ b/src/main/java/gift/dto/MemberDTO.java @@ -0,0 +1,52 @@ +package gift.dto; + +import gift.model.MemberRole; + +public class MemberDTO { + + private Long id; + private String email; + private String password; + private MemberRole role; + + public MemberDTO(String email, String password, MemberRole role) { + this.email = email; + this.password = password; + this.role = role; + if (this.role == null) { + this.role = MemberRole.COMMON_MEMBER; + } + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public MemberRole getRole() { + return role; + } + + public void setRole(MemberRole role) { + this.role = role; + } +} diff --git a/src/main/java/gift/exceptionAdvisor/MemberServiceException.java b/src/main/java/gift/exceptionAdvisor/MemberServiceException.java new file mode 100644 index 000000000..660ff4314 --- /dev/null +++ b/src/main/java/gift/exceptionAdvisor/MemberServiceException.java @@ -0,0 +1,21 @@ +package gift.exceptionAdvisor; + +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; + +public class MemberServiceException extends RuntimeException { + + private HttpStatus responseStatus; + + public MemberServiceException() { + } + + public MemberServiceException(String message, HttpStatus responseStatus) { + super(message); + this.responseStatus = responseStatus; + } + + public HttpStatusCode getStatusCode() { + return responseStatus; + } +} diff --git a/src/main/java/gift/model/Member.java b/src/main/java/gift/model/Member.java index 9adff91a7..c475a3cff 100644 --- a/src/main/java/gift/model/Member.java +++ b/src/main/java/gift/model/Member.java @@ -7,6 +7,13 @@ public class Member { private String password; private MemberRole role; + public Member(Long id, String email, String password, MemberRole role) { + this.id = id; + this.email = email; + this.password = password; + this.role = role; + } + public Long getId() { return id; } @@ -30,4 +37,12 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } + + public MemberRole getRole() { + return role; + } + + public void setRole(MemberRole role) { + this.role = role; + } } diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java new file mode 100644 index 000000000..811e075dc --- /dev/null +++ b/src/main/java/gift/service/MemberService.java @@ -0,0 +1,17 @@ +package gift.service; + +import gift.dto.LoginToken; +import gift.dto.MemberDTO; + +public interface MemberService { + + //회원가입을 진행한다. + public void register(MemberDTO memberDTO); + + //로그인을 진행한다. + public LoginToken login(MemberDTO memberDTO); + + //회원의 접근 권한을 확인한다. + public boolean checkRole(LoginToken loginToken); + +} diff --git a/src/main/java/gift/service/MemberServiceImpl.java b/src/main/java/gift/service/MemberServiceImpl.java new file mode 100644 index 000000000..0f629b82b --- /dev/null +++ b/src/main/java/gift/service/MemberServiceImpl.java @@ -0,0 +1,62 @@ +package gift.service; + +import gift.database.JdbcMemeberRepository; +import gift.dto.LoginToken; +import gift.dto.MemberDTO; +import gift.exceptionAdvisor.MemberServiceException; +import gift.model.Member; +import gift.model.MemberRole; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +public class MemberServiceImpl implements MemberService { + + private JdbcMemeberRepository jdbcMemeberRepository; + + public MemberServiceImpl(JdbcMemeberRepository jdbcMemeberRepository) { + this.jdbcMemeberRepository = jdbcMemeberRepository; + } + + @Override + public void register(MemberDTO memberDTO) { + if (checkEmailDuplication(memberDTO.getEmail())) { + throw new MemberServiceException("이메일이 중복됩니다", HttpStatus.FORBIDDEN); + } + + jdbcMemeberRepository.create(memberDTO.getEmail(), memberDTO.getPassword(), + MemberRole.COMMON_MEMBER.toString()); + } + + @Override + public LoginToken login(MemberDTO memberDTO) { + Member member = jdbcMemeberRepository.findByEmail(memberDTO.getEmail()); + + if (memberDTO.getPassword().equals(member.getPassword())) { + return new LoginToken(member.getEmail(), member.getRole()); + } + + throw new MemberServiceException("잘못된 로그인 시도입니다.", HttpStatus.FORBIDDEN); + } + + @Override + public boolean checkRole(LoginToken loginToken) { + return false; + } + + /** + * 이메일 중복 확인 + * + * @param email + * @return 중복된 이메일인 경우 true 반환 + */ + private boolean checkEmailDuplication(String email) { + try { + jdbcMemeberRepository.findByEmail(email); + return true; + } catch (EmptyResultDataAccessException e) { + return false; + } + } +} From 5f4d556836ce34e793e1ecf96df7feb0e74b5d25 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:02:17 +0900 Subject: [PATCH 27/53] =?UTF-8?q?feat=20:=20memberController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/MemberController.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/gift/controller/MemberController.java diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java new file mode 100644 index 000000000..bb852c81f --- /dev/null +++ b/src/main/java/gift/controller/MemberController.java @@ -0,0 +1,33 @@ +package gift.controller; + +import gift.dto.LoginToken; +import gift.dto.MemberDTO; +import gift.service.MemberService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/member") +@RestController +public class MemberController { + + private final MemberService memberService; + + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + + @PutMapping + public void register(@RequestBody MemberDTO memberDTO) { + memberService.register(memberDTO); + } + + @GetMapping("/login") + public LoginToken login(@RequestParam("email") String email, + @RequestParam("password") String password) { + return memberService.login(new MemberDTO(email, password, null)); + } +} From 45ce4a6b192479e09a7cb73ee4bd47de92d7a85f Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:03:14 +0900 Subject: [PATCH 28/53] =?UTF-8?q?refact=20:=20productexception=20HTTP=20st?= =?UTF-8?q?atus=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 예외Advisor에서 응답에 대한 httpstatus 없이 동작할 수 있도록 --- .../exceptionAdvisor/ProductExceptionAdvisor.java | 12 +++++++++--- .../exceptionAdvisor/ProductServiceException.java | 11 ++++++++++- src/main/java/gift/service/ProductServiceImpl.java | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java index 19fe26ea1..b5f9d74a2 100644 --- a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java +++ b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java @@ -2,7 +2,6 @@ import gift.dto.ExceptionResponseDTO; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -18,13 +17,20 @@ public ResponseEntity productValidationException( MethodArgumentNotValidException exception) { return new ResponseEntity<>(new ExceptionResponseDTO( exception.getBindingResult().getFieldError().getDefaultMessage()), - HttpStatus.BAD_REQUEST); + exception.getStatusCode()); } @ExceptionHandler(ProductServiceException.class) public ResponseEntity productServiceException( ProductServiceException exception) { return new ResponseEntity<>(new ExceptionResponseDTO(exception.getMessage()), - HttpStatus.BAD_REQUEST); + exception.getStatusCode()); + } + + @ExceptionHandler(MemberServiceException.class) + public ResponseEntity memberServiceException( + MemberServiceException exception) { + return new ResponseEntity<>(new ExceptionResponseDTO(exception.getMessage()), + exception.getStatusCode()); } } diff --git a/src/main/java/gift/exceptionAdvisor/ProductServiceException.java b/src/main/java/gift/exceptionAdvisor/ProductServiceException.java index 973978699..46723054b 100644 --- a/src/main/java/gift/exceptionAdvisor/ProductServiceException.java +++ b/src/main/java/gift/exceptionAdvisor/ProductServiceException.java @@ -1,11 +1,20 @@ package gift.exceptionAdvisor; +import org.springframework.http.HttpStatus; + public class ProductServiceException extends RuntimeException { + private HttpStatus responseStatus; + public ProductServiceException() { } - public ProductServiceException(String message) { + public ProductServiceException(String message, HttpStatus responseStatus) { super(message); + this.responseStatus = responseStatus; + } + + public HttpStatus getStatusCode() { + return responseStatus; } } diff --git a/src/main/java/gift/service/ProductServiceImpl.java b/src/main/java/gift/service/ProductServiceImpl.java index 2747dcf19..80a21c726 100644 --- a/src/main/java/gift/service/ProductServiceImpl.java +++ b/src/main/java/gift/service/ProductServiceImpl.java @@ -6,6 +6,7 @@ import gift.model.Product; import java.util.ArrayList; import java.util.List; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @Service @@ -68,7 +69,8 @@ public void delete(long id) { private void checkKakao(String productName) { if (productName.contains("카카오")) { - throw new ProductServiceException("카카오 문구는 md협의 이후 사용할 수 있습니다."); + throw new ProductServiceException("카카오 문구는 md협의 이후 사용할 수 있습니다.", + HttpStatus.BAD_REQUEST); } } } From 0caa2b39fbb6282e5b39bc341c5a8fb1cf00c9c5 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:03:58 +0900 Subject: [PATCH 29/53] =?UTF-8?q?test=20:=20memberController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 회원 가입, 중복된 이메일 가입 시도 --- .../gift/controller/MemberControllerTest.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/test/java/gift/controller/MemberControllerTest.java diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java new file mode 100644 index 000000000..cbb872d88 --- /dev/null +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -0,0 +1,89 @@ +package gift.controller; + +import gift.dto.MemberDTO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; +import org.springframework.web.reactive.function.BodyInserters; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class MemberControllerTest { + + @LocalServerPort + private int port; + private String baseUrl; + @Autowired + private WebTestClient webClient; + + @BeforeEach + void setUp() { + baseUrl = "http://localhost:" + port; + webClient = WebTestClient.bindToServer().baseUrl(baseUrl).build(); + } + + @Test + @DisplayName("회원가입 성공") + void registerMember() { + //given + String email = "abcd@gmail.com"; + String password = "abcd"; + MemberDTO dto = new MemberDTO(email, password, null); + + //when + ResponseSpec responseSpec = webClient.put().uri("/api/member") + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue((dto))).exchange(); + + //then + responseSpec.expectStatus().isOk(); + } + + @Test + @DisplayName("중복된 이메일로 회원가입 시도") + void registerDuplicateEmail() { + //given + String email = "abcd@gmail.com"; + + MemberDTO dto = new MemberDTO(email, "1234", null); + MemberDTO dto2 = new MemberDTO(email, "4567", null); + + //when + ResponseSpec responseSpec = webClient.put().uri("/api/member") + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue((dto))).exchange(); + ResponseSpec responseSpec2 = webClient.put().uri("/api/member") + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue((dto2))).exchange(); + + responseSpec.expectStatus().isOk(); + responseSpec2.expectStatus().isForbidden(); + } + /* + @Test + @DisplayName("이메일을 입력하지 않은 경우") + + @Test + @DisplayName("패스워드를 입력하지 않은 경우") + + @Test + @DisplayName("로그인 성공") + + @Test + @DisplayName("패스워드가 불일치한 경우") + + @Test + @DisplayName("회원가입이 되지않은 이메일로 로그인을 시도하는 경우") + + @Test + @DisplayName("로그인 성공으로 Token을 받아오기 성공") + + + + */ +} \ No newline at end of file From 771d69a0822646b2ddc7fa89e84fed815ca9f5ea Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:04:17 +0900 Subject: [PATCH 30/53] =?UTF-8?q?docs=20:=20=EB=AC=B8=EC=84=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8,=20=EC=88=98=EC=A0=95=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EB=93=B1=EC=97=90=20=EB=8C=80=ED=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ history.md | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bc3d3cc1b..85584d6ee 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,8 @@ Member.class : 회원모델 MemberRole.class : 회원 등급 enum class + +### 응답 코드 + +헤더나 토큰이 유효하지 않은 경우 -> 401 +잘못된 로그인, 비밀번호 찾기, 비밀번호 변경 요청 -> 403 diff --git a/history.md b/history.md index 76ebf3189..5e0cb95c7 100644 --- a/history.md +++ b/history.md @@ -32,4 +32,20 @@ 궁금증 : 모델에서 null 허용이 되는 wrapper class를 사용하는 것이 좋은 방법일까? 나쁜 방법일까? -[ ] Product 에 setter가 필요한가? -- \ No newline at end of file + +readme 와 history와 notion 3개의 중복된 문서 작성 문제... 굳이 필요없는 내용을 많이 남긴다는 생각 자체가 잘못된 것 같기도? +주말에 특정한 규칙을 정하기 필요 + +-[ ] 문서정리규칙 정하기 + +### 수정해야할 것 + +1. 규격화된 테스트로 전체적인 동작에 문제가 없는지 확인하기 +2. 컨트롤러 - 서비스 - 모델 사이에서 명확하게 책임 정해서 작업하기 +3. JWT 공부해서 제대로 적용시키기 (비밀키 숨기기) +4. 인증토큰에 대해서 서버에서 어떻게 인증토큰을 통해서 권한을 설정하는지 제대로 알고 적용시키기 + +- [ ] 테스트코드를 위한 보일러플레이트 클래스를 만들어서 사용해도 되는가? - 좋은가? 나쁜가? + +- [ ] Repository는 정확히 어떤 책임을 지고 있는 것인지? 새로운 멤버를 등록하였다면 이걸 다시 Model로 반환해줘야하는가? + From 997d53cf280c3673858bfb0d1ad735b4e4e7233f Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:34:01 +0900 Subject: [PATCH 31/53] =?UTF-8?q?test=20:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/MemberControllerTest.java | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java index cbb872d88..bc0e1ddc3 100644 --- a/src/test/java/gift/controller/MemberControllerTest.java +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -1,5 +1,6 @@ package gift.controller; +import gift.dto.LoginToken; import gift.dto.MemberDTO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -36,9 +37,7 @@ void registerMember() { MemberDTO dto = new MemberDTO(email, password, null); //when - ResponseSpec responseSpec = webClient.put().uri("/api/member") - .accept(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue((dto))).exchange(); + ResponseSpec responseSpec = registerMemberPutRequest(dto); //then responseSpec.expectStatus().isOk(); @@ -54,26 +53,61 @@ void registerDuplicateEmail() { MemberDTO dto2 = new MemberDTO(email, "4567", null); //when - ResponseSpec responseSpec = webClient.put().uri("/api/member") - .accept(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue((dto))).exchange(); - ResponseSpec responseSpec2 = webClient.put().uri("/api/member") - .accept(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue((dto2))).exchange(); + ResponseSpec responseSpec = registerMemberPutRequest(dto); + ResponseSpec responseSpec2 = registerMemberPutRequest(dto2); responseSpec.expectStatus().isOk(); responseSpec2.expectStatus().isForbidden(); } - /* + @Test @DisplayName("이메일을 입력하지 않은 경우") + void nullEmail() { + //given + MemberDTO dto = new MemberDTO(null, "1234", null); + + //when + ResponseSpec responseSpec = registerMemberPutRequest(dto); + + //then + responseSpec.expectStatus().isBadRequest(); + } @Test @DisplayName("패스워드를 입력하지 않은 경우") + void nullPassword() { + //given + MemberDTO dto = new MemberDTO("abcd@abcd", null, null); + + //when + ResponseSpec responseSpec = registerMemberPutRequest(dto); + + //then + responseSpec.expectStatus().isBadRequest(); + } + @Test @DisplayName("로그인 성공") + void login() { + //given + String email = "abcd@gmail.com"; + String password = "abcd"; + MemberDTO dto = new MemberDTO(email, password, null); + ResponseSpec responseSpec = registerMemberPutRequest(dto); + + //when + ResponseSpec responseSeec = webClient.get().uri(uriBuilder -> uriBuilder + .path("/api/member/login") + .queryParam("email", dto.getEmail()) + .queryParam("password", dto.getPassword()) + .build()).accept(MediaType.APPLICATION_JSON).exchange(); + //then + responseSeec.expectStatus().isOk(); + responseSeec.expectBody(LoginToken.class); + } + /* @Test @DisplayName("패스워드가 불일치한 경우") @@ -82,8 +116,12 @@ void registerDuplicateEmail() { @Test @DisplayName("로그인 성공으로 Token을 받아오기 성공") + */ + private ResponseSpec registerMemberPutRequest(MemberDTO dto) { + ResponseSpec responseSpec = webClient.put().uri("/api/member") + .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(dto)).exchange(); + return responseSpec; + } - - */ } \ No newline at end of file From 9382b541c576e388ba617f120ad6f428fc00870b Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 14:34:51 +0900 Subject: [PATCH 32/53] =?UTF-8?q?refact=20:=20JWT=20=EB=B3=B4=EC=95=88=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 시크릿키의 길이로 인한 암호화 과정 문제 수정 --- src/main/java/gift/dto/LoginToken.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/gift/dto/LoginToken.java b/src/main/java/gift/dto/LoginToken.java index 5e10d0557..3413b33bf 100644 --- a/src/main/java/gift/dto/LoginToken.java +++ b/src/main/java/gift/dto/LoginToken.java @@ -2,14 +2,12 @@ import gift.model.MemberRole; import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.security.Keys; -import java.nio.charset.StandardCharsets; +import io.jsonwebtoken.Jwts.SIG; import java.util.Objects; public class LoginToken { private String token; - private String secretKey = "testword"; public LoginToken() { } @@ -18,7 +16,7 @@ public LoginToken(String email, MemberRole role) { this.token = Jwts.builder() .setSubject(email) .claim("role", role.toString()) - .signWith(Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))) + .signWith(SIG.HS256.key().build()) .compact(); } From 3c1c77001980a329a0c5b45c3bc7493f8b68274d Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 16:08:54 +0900 Subject: [PATCH 33/53] =?UTF-8?q?docs=20:=20step3=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 85584d6ee..d22a1b4b5 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ 토큰은 JWT 를 사용하도록 한다. 관리자는 회원을 조회 추가 수정 삭제 할 수 있다. -- [ ] feat : 회원 모델 만들기 -- [ ] feat : 회원 Repository 만들기 -- [ ] feat : 회원 가입 서비스 만들기 -- [ ] feat : 회원 가입 컨트롤러 만들기 -- [ ] feat : 로그인 서비스 만들기 -- [ ] feat : 인증 서비스 만들기 -- [ ] feat : 인증 컨트롤러 만들기 +- [X] feat : 회원 모델 만들기 +- [X] feat : 회원 Repository 만들기 +- [X] feat : 회원 가입 서비스 만들기 +- [X] feat : 회원 가입 컨트롤러 만들기 +- [X] feat : 로그인 서비스 만들기 +- [X] feat : 인증 서비스 만들기 +- [X] feat : 인증 컨트롤러 만들기 ### 회원 모델 만들기 @@ -51,3 +51,21 @@ MemberRole.class : 회원 등급 enum class 헤더나 토큰이 유효하지 않은 경우 -> 401 잘못된 로그인, 비밀번호 찾기, 비밀번호 변경 요청 -> 403 + +## milestone 3 + +위시 리스트 + +위시 리스트는 일종의 장바구니로 생각할 수 있다. +위시 리스트에 등록된 상품 목록을 조회할 수 있다. +위시 리스트에 상품을 추가할 수 있다. +위시 리스트에 담긴 상품을 삭제할 수 있다. +사용자 정보는 요청 헤더의 Authorization 필드를 사용한다. + +- [ ] feat : 위시 리스트 모델 만들기 +- [ ] feat : 위시리스트 Repository 만들기 +- [ ] feat : 위시리스트 서비스 만들기 +- [ ] feat : 위시리스트 컨트롤러 만들기 +- [ ] feat : 인증을 위한 ArgumentResolver 만들기 +- [ ] test : 테스트 코드로 동작 확인 + From 953b831b8c67c08c254dbbc85cd340475398dad3 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sat, 6 Jul 2024 16:24:10 +0900 Subject: [PATCH 34/53] =?UTF-8?q?feat=20:=20=EC=9C=84=EC=8B=9C=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=AA=A8=EB=8D=B8=20=EB=A7=8C=EB=93=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/gift/model/WishList.java | 36 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/main/java/gift/model/WishList.java diff --git a/README.md b/README.md index d22a1b4b5..554894654 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ MemberRole.class : 회원 등급 enum class 위시 리스트에 담긴 상품을 삭제할 수 있다. 사용자 정보는 요청 헤더의 Authorization 필드를 사용한다. -- [ ] feat : 위시 리스트 모델 만들기 +- [X] feat : 위시 리스트 모델 만들기 - [ ] feat : 위시리스트 Repository 만들기 - [ ] feat : 위시리스트 서비스 만들기 - [ ] feat : 위시리스트 컨트롤러 만들기 diff --git a/src/main/java/gift/model/WishList.java b/src/main/java/gift/model/WishList.java new file mode 100644 index 000000000..b2e3c88a8 --- /dev/null +++ b/src/main/java/gift/model/WishList.java @@ -0,0 +1,36 @@ +package gift.model; + +import java.util.Map; + +public class WishList { + + private Long id; + private Long memberId; + private Map wishList; + + public WishList(Long id, Long memberId, Map wishList) { + this.id = id; + this.memberId = memberId; + this.wishList = wishList; + } + + public Long getId() { + return id; + } + + public Long getMemberId() { + return memberId; + } + + public Map getWishList() { + return wishList; + } + + public void updateProduct(Long productId, Integer count) { + wishList.put(productId, count); + } + + public void deleteProduct(Long productId) { + wishList.remove(productId); + } +} From 3b73a9cd51ed0c87e09d3c85fba28f35b7b1025a Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 02:36:44 +0900 Subject: [PATCH 35/53] =?UTF-8?q?feat=20:=20=EC=9C=84=EC=8B=9C=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20Repository=20=EB=A7=8C=EB=93=A4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../gift/database/JdbcWishListRepository.java | 86 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/main/java/gift/database/JdbcWishListRepository.java diff --git a/README.md b/README.md index 554894654..3032f121a 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ MemberRole.class : 회원 등급 enum class 사용자 정보는 요청 헤더의 Authorization 필드를 사용한다. - [X] feat : 위시 리스트 모델 만들기 -- [ ] feat : 위시리스트 Repository 만들기 +- [X] feat : 위시리스트 Repository 만들기 - [ ] feat : 위시리스트 서비스 만들기 - [ ] feat : 위시리스트 컨트롤러 만들기 - [ ] feat : 인증을 위한 ArgumentResolver 만들기 diff --git a/src/main/java/gift/database/JdbcWishListRepository.java b/src/main/java/gift/database/JdbcWishListRepository.java new file mode 100644 index 000000000..6b2dbbfb7 --- /dev/null +++ b/src/main/java/gift/database/JdbcWishListRepository.java @@ -0,0 +1,86 @@ +package gift.database; + +import gift.model.WishList; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import javax.sql.DataSource; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +@Repository +public class JdbcWishListRepository { + + private final JdbcTemplate template; + + public JdbcWishListRepository(DataSource dataSource) { + this.template = new JdbcTemplate(dataSource); + createTable(); + } + + private void createTable() { + template.update("create table if not exists wishlist(" + + "id long primary key auto_increment, " + + "member_id long not null," + + "product_id long not null," + + "product_value int not null)"); + } + + public WishList findByMemeberId(Long memberId) { + String sql = "select * from wishlist where member_id = ?"; + template.queryForObject(sql, wishListRowMapper()); + } + + public void insertWishList(WishList wishList) { + String sql = "insert into wishlist (member_id, product_id, product_value) values (?,?,?)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + List productIds = wishList.getWishList().keySet().stream().toList(); + + template.batchUpdate(sql, new BatchPreparedStatementSetter() { + + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + + ps.setLong(1, wishList.getMemberId()); + ps.setLong(2, productIds.get(i)); + ps.setInt(3, wishList.getWishList().get(productIds.get(i))); + } + + @Override + public int getBatchSize() { + return 100; + } + }); + } + + public void updateWishList(WishList wishList) { + String sql = "update wishlist set member_id = ?, product_id = ?," + + " product_value = ? where member_id =?,product_id=?"; + for (long productId : wishList.getWishList().keySet()) { + template.update(sql, wishList.getMemberId(), productId, + wishList.getWishList().get(productId)); + } + } + + public void deleteWishList(long memberId, long productId) { + String sql = "delete from wishlist where member_id = ? and product_id = ?"; + template.update(sql, memberId, productId); + } + + private RowMapper wishListRowMapper() { + return (rs, rowNum) -> { + WishList wishList = new WishList( + rs.getLong("id"), + rs.getLong("member_id"), + new HashMap<>() + ); + wishList.updateProduct(rs.getLong("product_id"), rs.getInt("product_value")); + return wishList; + }; + } +} From ab8035df08f60a122377454e52a4d25dd872db19 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 02:37:46 +0900 Subject: [PATCH 36/53] =?UTF-8?q?feat=20:=20=EC=9C=84=EC=8B=9C=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../java/gift/service/WishListService.java | 15 +++++++ .../gift/service/WishListServiceImpl.java | 41 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/main/java/gift/service/WishListService.java create mode 100644 src/main/java/gift/service/WishListServiceImpl.java diff --git a/README.md b/README.md index 3032f121a..a9134df93 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ MemberRole.class : 회원 등급 enum class - [X] feat : 위시 리스트 모델 만들기 - [X] feat : 위시리스트 Repository 만들기 -- [ ] feat : 위시리스트 서비스 만들기 +- [X] feat : 위시리스트 서비스 만들기 - [ ] feat : 위시리스트 컨트롤러 만들기 - [ ] feat : 인증을 위한 ArgumentResolver 만들기 - [ ] test : 테스트 코드로 동작 확인 diff --git a/src/main/java/gift/service/WishListService.java b/src/main/java/gift/service/WishListService.java new file mode 100644 index 000000000..9bc5e0c70 --- /dev/null +++ b/src/main/java/gift/service/WishListService.java @@ -0,0 +1,15 @@ +package gift.service; + +import gift.dto.WishListDTO; + +public interface WishListService { + + void addNewProduct(long memberId, long productId); + + void deleteProduct(long memberId, long productId); + + void updateProduct(long memberId, long productId, int productValue); + + WishListDTO getWishList(long memberId); + +} diff --git a/src/main/java/gift/service/WishListServiceImpl.java b/src/main/java/gift/service/WishListServiceImpl.java new file mode 100644 index 000000000..a495905dd --- /dev/null +++ b/src/main/java/gift/service/WishListServiceImpl.java @@ -0,0 +1,41 @@ +package gift.service; + +import gift.database.JdbcWishListRepository; +import gift.dto.WishListDTO; +import gift.model.WishList; +import java.util.HashMap; +import org.springframework.stereotype.Service; + +@Service +public class WishListServiceImpl implements WishListService { + + private JdbcWishListRepository jdbcWishListRepository; + + public WishListServiceImpl(JdbcWishListRepository jdbcWishListRepository) { + this.jdbcWishListRepository = jdbcWishListRepository; + } + + @Override + public void addNewProduct(long memberId, long productId) { + WishList wishList = new WishList(null, memberId, new HashMap<>()); + wishList.updateProduct(productId, 1); + jdbcWishListRepository.insertWishList(wishList); + } + + @Override + public void deleteProduct(long memberId, long productId) { + jdbcWishListRepository.deleteWishList(memberId, productId); + } + + @Override + public void updateProduct(long memberId, long productId, int productValue) { + WishList wishList = jdbcWishListRepository.findByMemeberId(memberId); + wishList.updateProduct(productId, productValue); + jdbcWishListRepository.updateWishList(wishList); + } + + @Override + public WishListDTO getWishList(long memberId) { + return null; + } +} From ac1b086706dcec4ccb6253e4da86e8414b8a0eb4 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 02:39:13 +0900 Subject: [PATCH 37/53] =?UTF-8?q?refact=20:=20LoginToken=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=9D=84=20=EC=9C=84=ED=95=B4=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JWT는 추후 구현 진행하도록 일부 수정 --- .../gift/ArgumentResolver/LoginMember.java | 12 +++++++ .../LoginMemberArgumentResolver.java | 34 +++++++++++++++++++ .../gift/controller/MemberController.java | 2 +- .../java/gift/{dto => model}/LoginToken.java | 30 +++++++++++++--- src/main/java/gift/service/MemberService.java | 3 +- .../java/gift/service/MemberServiceImpl.java | 16 +++++++-- .../gift/controller/MemberControllerTest.java | 2 +- 7 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 src/main/java/gift/ArgumentResolver/LoginMember.java create mode 100644 src/main/java/gift/ArgumentResolver/LoginMemberArgumentResolver.java rename src/main/java/gift/{dto => model}/LoginToken.java (54%) diff --git a/src/main/java/gift/ArgumentResolver/LoginMember.java b/src/main/java/gift/ArgumentResolver/LoginMember.java new file mode 100644 index 000000000..e010e9406 --- /dev/null +++ b/src/main/java/gift/ArgumentResolver/LoginMember.java @@ -0,0 +1,12 @@ +package gift.ArgumentResolver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface LoginMember { + //컨트롤러에서 사용을 위해 +} diff --git a/src/main/java/gift/ArgumentResolver/LoginMemberArgumentResolver.java b/src/main/java/gift/ArgumentResolver/LoginMemberArgumentResolver.java new file mode 100644 index 000000000..fffcf2aae --- /dev/null +++ b/src/main/java/gift/ArgumentResolver/LoginMemberArgumentResolver.java @@ -0,0 +1,34 @@ +package gift.ArgumentResolver; + +import gift.service.MemberService; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { + + private final MemberService memberService; + + public LoginMemberArgumentResolver(MemberService memberService) { + this.memberService = memberService; + } + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(LoginMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + String token = request.getHeader("Authorization"); + if (token == null) { + return null; + } + return memberService.getLoginUser(token); + } +} diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java index bb852c81f..fbf7b85ed 100644 --- a/src/main/java/gift/controller/MemberController.java +++ b/src/main/java/gift/controller/MemberController.java @@ -1,7 +1,7 @@ package gift.controller; -import gift.dto.LoginToken; import gift.dto.MemberDTO; +import gift.model.LoginToken; import gift.service.MemberService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; diff --git a/src/main/java/gift/dto/LoginToken.java b/src/main/java/gift/model/LoginToken.java similarity index 54% rename from src/main/java/gift/dto/LoginToken.java rename to src/main/java/gift/model/LoginToken.java index 3413b33bf..84ab24956 100644 --- a/src/main/java/gift/dto/LoginToken.java +++ b/src/main/java/gift/model/LoginToken.java @@ -1,29 +1,49 @@ -package gift.dto; +package gift.model; -import gift.model.MemberRole; -import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts.SIG; +import java.security.Key; import java.util.Objects; public class LoginToken { private String token; + private Key key = SIG.HS256.key().build(); + + private String email; + private String role; + private Long memberId; + public LoginToken() { } - public LoginToken(String email, MemberRole role) { + public LoginToken(Long memberId, String email, MemberRole role) { + this.token = email + ":" + role.toString(); + /* JWT 학습 이후 구현하기 this.token = Jwts.builder() .setSubject(email) .claim("role", role.toString()) - .signWith(SIG.HS256.key().build()) + .signWith(key) .compact(); + + */ + this.email = email; + this.role = role.toString(); } public String getToken() { return token; } + public Long getUserId() { + if (token != null) { + return memberId; + } + //JwtParser parser = Jwts.parser().build(); + return null; + + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index 811e075dc..6a561d851 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -1,7 +1,7 @@ package gift.service; -import gift.dto.LoginToken; import gift.dto.MemberDTO; +import gift.model.LoginToken; public interface MemberService { @@ -14,4 +14,5 @@ public interface MemberService { //회원의 접근 권한을 확인한다. public boolean checkRole(LoginToken loginToken); + MemberDTO getLoginUser(String token); } diff --git a/src/main/java/gift/service/MemberServiceImpl.java b/src/main/java/gift/service/MemberServiceImpl.java index 0f629b82b..2d1253b88 100644 --- a/src/main/java/gift/service/MemberServiceImpl.java +++ b/src/main/java/gift/service/MemberServiceImpl.java @@ -1,9 +1,9 @@ package gift.service; import gift.database.JdbcMemeberRepository; -import gift.dto.LoginToken; import gift.dto.MemberDTO; import gift.exceptionAdvisor.MemberServiceException; +import gift.model.LoginToken; import gift.model.Member; import gift.model.MemberRole; import org.springframework.dao.EmptyResultDataAccessException; @@ -34,7 +34,11 @@ public LoginToken login(MemberDTO memberDTO) { Member member = jdbcMemeberRepository.findByEmail(memberDTO.getEmail()); if (memberDTO.getPassword().equals(member.getPassword())) { - return new LoginToken(member.getEmail(), member.getRole()); + LoginToken loginToken = new LoginToken(member.getId(), member.getEmail(), + member.getRole()); + jdbcMemeberRepository.update(member.getEmail(), member.getPassword(), + member.getRole().toString(), loginToken.getToken()); + return loginToken; } throw new MemberServiceException("잘못된 로그인 시도입니다.", HttpStatus.FORBIDDEN); @@ -45,6 +49,14 @@ public boolean checkRole(LoginToken loginToken) { return false; } + @Override + public MemberDTO getLoginUser(String token) { + Member member = jdbcMemeberRepository.findByToken(token); + + return new MemberDTO(member.getEmail(), null, member.getRole()); + } + + /** * 이메일 중복 확인 * diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java index bc0e1ddc3..b752ab422 100644 --- a/src/test/java/gift/controller/MemberControllerTest.java +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -1,7 +1,7 @@ package gift.controller; -import gift.dto.LoginToken; import gift.dto.MemberDTO; +import gift.model.LoginToken; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; From 2c3d3506c098819b16ce9d19a40e110604d9877e Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 12:51:07 +0900 Subject: [PATCH 38/53] =?UTF-8?q?refact=20:=20member=20Role=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Role에 대한 관리 및 검사 추가 --- src/main/java/gift/controller/MemberController.java | 4 ++++ .../java/gift/database/JdbcMemeberRepository.java | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java index fbf7b85ed..b5ea5a66a 100644 --- a/src/main/java/gift/controller/MemberController.java +++ b/src/main/java/gift/controller/MemberController.java @@ -2,6 +2,7 @@ import gift.dto.MemberDTO; import gift.model.LoginToken; +import gift.model.MemberRole; import gift.service.MemberService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -22,6 +23,9 @@ public MemberController(MemberService memberService) { @PutMapping public void register(@RequestBody MemberDTO memberDTO) { + if (memberDTO.getRole() == null) { + memberDTO.setRole(MemberRole.COMMON_MEMBER); + } memberService.register(memberDTO); } diff --git a/src/main/java/gift/database/JdbcMemeberRepository.java b/src/main/java/gift/database/JdbcMemeberRepository.java index 64af3f96e..50f98305c 100644 --- a/src/main/java/gift/database/JdbcMemeberRepository.java +++ b/src/main/java/gift/database/JdbcMemeberRepository.java @@ -47,6 +47,16 @@ public void create(String email, String password, String role) { }, keyHolder); } + public Member findByToken(String token) { + String sql = "select * from member where token = ?"; + return template.queryForObject(sql, memberRowMapper(), token); + } + + public void update(String email, String password, String role, String token) { + String sql = "update member set password = ?, role = ?, token = ? where email = ?"; + template.update(sql, password, role, token, email); + } + private RowMapper memberRowMapper() { return (rs, rowNum) -> new Member( rs.getLong("id"), @@ -54,4 +64,6 @@ private RowMapper memberRowMapper() { rs.getString("password"), MemberRole.valueOf(rs.getString("role"))); } + + } From dd1d3394706fdbab020d10a6f40867676fefa2e4 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 12:51:56 +0900 Subject: [PATCH 39/53] =?UTF-8?q?refact=20:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit user -> member로 통일성 있는 사용 --- src/main/java/gift/model/LoginToken.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gift/model/LoginToken.java b/src/main/java/gift/model/LoginToken.java index 84ab24956..ee7dbcdb5 100644 --- a/src/main/java/gift/model/LoginToken.java +++ b/src/main/java/gift/model/LoginToken.java @@ -35,7 +35,7 @@ public String getToken() { return token; } - public Long getUserId() { + public Long getMemberId() { if (token != null) { return memberId; } From 2b72758344e142e8e092e988cae0def7f704293e Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 12:52:33 +0900 Subject: [PATCH 40/53] =?UTF-8?q?fix=20:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/gift/database/JdbcWishListRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gift/database/JdbcWishListRepository.java b/src/main/java/gift/database/JdbcWishListRepository.java index 6b2dbbfb7..491e45ffe 100644 --- a/src/main/java/gift/database/JdbcWishListRepository.java +++ b/src/main/java/gift/database/JdbcWishListRepository.java @@ -33,7 +33,7 @@ private void createTable() { public WishList findByMemeberId(Long memberId) { String sql = "select * from wishlist where member_id = ?"; - template.queryForObject(sql, wishListRowMapper()); + return template.queryForObject(sql, wishListRowMapper()); } public void insertWishList(WishList wishList) { From 98a90c22c1eeb7caa1e82e8b62fa184eee505a83 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 12:52:48 +0900 Subject: [PATCH 41/53] =?UTF-8?q?feat=20:=20wishlist=20controller=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/WishListController.java | 51 ++++++++++++ src/main/java/gift/dto/WishListDTO.java | 32 ++++++++ .../controller/WishListControllerTest.java | 77 +++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 src/main/java/gift/controller/WishListController.java create mode 100644 src/main/java/gift/dto/WishListDTO.java create mode 100644 src/test/java/gift/controller/WishListControllerTest.java diff --git a/src/main/java/gift/controller/WishListController.java b/src/main/java/gift/controller/WishListController.java new file mode 100644 index 000000000..e7af5051f --- /dev/null +++ b/src/main/java/gift/controller/WishListController.java @@ -0,0 +1,51 @@ +package gift.controller; + +import gift.ArgumentResolver.LoginMember; +import gift.dto.MemberDTO; +import gift.dto.WishListDTO; +import gift.service.WishListService; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("api/member/{memberid}/wishlist") +@RestController +public class WishListController { + + private final WishListService wishListService; + + public WishListController(WishListService wishListService) { + this.wishListService = wishListService; + } + + //상품 리스트 조회 + @GetMapping + public WishListDTO getWishList(@PathVariable int memberid, @LoginMember MemberDTO memberDTO) { + return wishListService.getWishList(memberid); + } + + //상품 추가 + @PutMapping + public void addWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { + wishListService.addNewProduct(memberid, wishListDTO.getProductId()); + } + + //상품 삭제 + @DeleteMapping + public void deleteWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { + wishListService.deleteProduct(memberid, wishListDTO.getProductId()); + } + + //상품 수정 + @PutMapping + public void updateWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { + wishListService.updateProduct(memberid, wishListDTO.getProductId(), + wishListDTO.getProductValue()); + } + + +} diff --git a/src/main/java/gift/dto/WishListDTO.java b/src/main/java/gift/dto/WishListDTO.java new file mode 100644 index 000000000..32516d004 --- /dev/null +++ b/src/main/java/gift/dto/WishListDTO.java @@ -0,0 +1,32 @@ +package gift.dto; + +public class WishListDTO { + + private Long memberId; + private Long productId; + private Integer productValue; + + public Long getMemberId() { + return memberId; + } + + public void setMemberId(Long memberId) { + this.memberId = memberId; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Integer getProductValue() { + return productValue; + } + + public void setProductValue(Integer productValue) { + this.productValue = productValue; + } +} diff --git a/src/test/java/gift/controller/WishListControllerTest.java b/src/test/java/gift/controller/WishListControllerTest.java new file mode 100644 index 000000000..9c7a15aa3 --- /dev/null +++ b/src/test/java/gift/controller/WishListControllerTest.java @@ -0,0 +1,77 @@ +package gift.controller; + +import gift.dto.WishListDTO; +import gift.model.LoginToken; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; +import org.springframework.web.reactive.function.BodyInserters; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class WishListControllerTest { + + @LocalServerPort + private int port; + private String baseUrl; + @Autowired + private WebTestClient webClient; + + @BeforeEach + void setUp() { + baseUrl = "http://localhost:" + port; + webClient = WebTestClient.bindToServer().baseUrl(baseUrl).build(); + } + + @Test + @DisplayName("위시 리스트 추가") + void addWishList() { + //given + LoginToken loginToken = registerAndLogin("test", "123"); + + //when + addWishListPutRequest(loginToken); + + //then + + } + + + @Test + @DisplayName("위시 리스트 조회") + void getWishList() { + + } + + @Test + @DisplayName("위시 리스트 아이템 수량 변경") + void updateWishList() { + + } + + @Test + @DisplayName("위시 리스트 아이템 삭제") + void deleteWishList() { + + } + + private LoginToken registerAndLogin(String email, String password) { + return null; + } + + private void addWishListPutRequest(long memberId, WishListDTO wishListDTO) { + ResponseSpec responseSpec = webClient.put().uri(uriBuilder -> { + return uriBuilder + .path("/api/member") + .queryParam("memberId", loginToken.getMemberId()) + .path("/wishlist") + .build(); + }).accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(wishListDTO)).exchange(); + } +} \ No newline at end of file From 098ac4479ae4751679bb1a106907e0ea039ed6e4 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 13:52:23 +0900 Subject: [PATCH 42/53] =?UTF-8?q?refact=20:=20repository=20=EA=B7=9C?= =?UTF-8?q?=EA=B2=A9=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 일관성 있는 규칙을 적용 --- .../gift/database/JdbcMemeberRepository.java | 30 +++++++++++++------ .../gift/database/JdbcProductRepository.java | 26 +++++++--------- .../java/gift/service/MemberServiceImpl.java | 4 +-- .../java/gift/service/ProductServiceImpl.java | 18 +++++------ 4 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/main/java/gift/database/JdbcMemeberRepository.java b/src/main/java/gift/database/JdbcMemeberRepository.java index 50f98305c..f880524bf 100644 --- a/src/main/java/gift/database/JdbcMemeberRepository.java +++ b/src/main/java/gift/database/JdbcMemeberRepository.java @@ -30,10 +30,6 @@ private void createTable() { + "token varchar(255))"); } - public Member findByEmail(String email) { - String sql = "select * from member where email = ?"; - return template.queryForObject(sql, memberRowMapper(), email); - } public void create(String email, String password, String role) { String sql = "insert into member (email, password,role) values (?, ?, ?)"; @@ -47,16 +43,32 @@ public void create(String email, String password, String role) { }, keyHolder); } + public void update(long id, Member member) { + String sql = "update member set email=?, password=?, role=?, token=? where id=?"; + template.update(sql, member.getEmail(), member.getPassword(), member.getRole(), + member.getToken(), id); + } + + public void delete(long id) { + String sql = "delete from member where id = ?"; + template.update(sql, id); + } + + public Member findById(long id) { + String sql = "select * from member where id = ?"; + return template.queryForObject(sql, memberRowMapper(), id); + } + + public Member findByEmail(String email) { + String sql = "select * from member where email = ?"; + return template.queryForObject(sql, memberRowMapper(), email); + } + public Member findByToken(String token) { String sql = "select * from member where token = ?"; return template.queryForObject(sql, memberRowMapper(), token); } - public void update(String email, String password, String role, String token) { - String sql = "update member set password = ?, role = ?, token = ? where email = ?"; - template.update(sql, password, role, token, email); - } - private RowMapper memberRowMapper() { return (rs, rowNum) -> new Member( rs.getLong("id"), diff --git a/src/main/java/gift/database/JdbcProductRepository.java b/src/main/java/gift/database/JdbcProductRepository.java index 10db1ac7b..c06c66a93 100644 --- a/src/main/java/gift/database/JdbcProductRepository.java +++ b/src/main/java/gift/database/JdbcProductRepository.java @@ -28,46 +28,42 @@ private void createTable() { + "imageUrl varchar(255))"); } - /** - * 새로운 상품 추가 - * @param product id 값은 무시됨. - * @return DB에 저장된 id값이 포함된 객체 반환 - */ - public Product insertProduct(Product product) { + + public Product create(String name, int price, String imageUrl) { String sql = "insert into product (name, price, imageUrl) values (?,?,?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); template.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql,new String[]{"id"}); - ps.setString(1, product.getName()); - ps.setInt(2, product.getPrice()); - ps.setString(3, product.getImageUrl()); + ps.setString(1, name); + ps.setInt(2, price); + ps.setString(3, imageUrl); return ps; },keyHolder); long key = keyHolder.getKey().longValue(); - product.setId(key); - return product; + + return new Product(key, name, price, imageUrl); } //기존 상품 수정 - public void updateProduct(Long id,Product product) { + public void update(long id, Product product) { String sql = "update product set name = ?, price = ?, imageUrl = ? where id = ?"; template.update(sql,product.getName(),product.getPrice(),product.getImageUrl(),id); } //상품 단일 조회 - public Product getProduct(Long id) { + public Product findById(long id) { String sql = "select * from product where id = ?"; return template.queryForObject(sql,productRowMapper(),id); } //상품 전체 조회 - public List findAllProducts() { + public List findAll() { String sql = "select * from product"; return template.query(sql,productRowMapper()); } //상품 삭제 - public void deleteProduct(Long id) { + public void delete(long id) { String sql = "delete from product where id = ?"; template.update(sql,id); } diff --git a/src/main/java/gift/service/MemberServiceImpl.java b/src/main/java/gift/service/MemberServiceImpl.java index 2d1253b88..cfd51801f 100644 --- a/src/main/java/gift/service/MemberServiceImpl.java +++ b/src/main/java/gift/service/MemberServiceImpl.java @@ -36,8 +36,8 @@ public LoginToken login(MemberDTO memberDTO) { if (memberDTO.getPassword().equals(member.getPassword())) { LoginToken loginToken = new LoginToken(member.getId(), member.getEmail(), member.getRole()); - jdbcMemeberRepository.update(member.getEmail(), member.getPassword(), - member.getRole().toString(), loginToken.getToken()); + member.setToken(loginToken.getToken()); + jdbcMemeberRepository.update(member.getId(), member); return loginToken; } diff --git a/src/main/java/gift/service/ProductServiceImpl.java b/src/main/java/gift/service/ProductServiceImpl.java index 80a21c726..a6facd256 100644 --- a/src/main/java/gift/service/ProductServiceImpl.java +++ b/src/main/java/gift/service/ProductServiceImpl.java @@ -20,7 +20,7 @@ public ProductServiceImpl(JdbcProductRepository jdbcProductRepository) { @Override public List readAll() { - var products = jdbcProductRepository.findAllProducts(); + var products = jdbcProductRepository.findAll(); List productDTOList = new ArrayList<>(); for (var product : products) { //DTO로 전환 @@ -34,37 +34,37 @@ public List readAll() { @Override public void create(ProductDTO prod) { checkKakao(prod.getName()); - jdbcProductRepository.insertProduct( + jdbcProductRepository.create( new Product(null, prod.getName(), prod.getPrice(), prod.getImageUrl())); } @Override public void updateName(long id, String name) { - var prod = jdbcProductRepository.getProduct(id); + var prod = jdbcProductRepository.findById(id); checkKakao(prod.getName()); prod.setName(name); - jdbcProductRepository.updateProduct(id, prod); + jdbcProductRepository.update(id, prod); } @Override public void updatePrice(long id, int price) { - var prod = jdbcProductRepository.getProduct(id); + var prod = jdbcProductRepository.findById(id); prod.setPrice(price); - jdbcProductRepository.updateProduct(id, prod); + jdbcProductRepository.update(id, prod); } @Override public void updateImageUrl(long id, String url) { - var prod = jdbcProductRepository.getProduct(id); + var prod = jdbcProductRepository.findById(id); prod.setImageUrl(url); - jdbcProductRepository.updateProduct(id, prod); + jdbcProductRepository.update(id, prod); } @Override public void delete(long id) { - jdbcProductRepository.deleteProduct(id); + jdbcProductRepository.delete(id); } private void checkKakao(String productName) { From c9af84daa8e9c99013c91c970d0db102fcfac31c Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 14:00:12 +0900 Subject: [PATCH 43/53] =?UTF-8?q?fix=20:=20Product=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관련 오류 수정 --- history.md | 27 +++++++++++++ .../gift/controller/WishListController.java | 11 +++--- src/main/java/gift/model/Member.java | 9 +++++ .../java/gift/service/ProductServiceImpl.java | 8 ++-- .../controller/WishListControllerTest.java | 10 ++--- .../java/gift/service/MemberServiceTest.java | 39 +++++++++++++++++++ 6 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 src/test/java/gift/service/MemberServiceTest.java diff --git a/history.md b/history.md index 5e0cb95c7..b9ef8aeb1 100644 --- a/history.md +++ b/history.md @@ -49,3 +49,30 @@ readme 와 history와 notion 3개의 중복된 문서 작성 문제... 굳이 - [ ] Repository는 정확히 어떤 책임을 지고 있는 것인지? 새로운 멤버를 등록하였다면 이걸 다시 Model로 반환해줘야하는가? +- [ ] Repository는 무엇을 얼마만큼 제어해야할까? + 만약 Model의 내부 값이 변하면, 이것은 service에서 하나하나 분리해서 repository를 업데이트 해줘야하는가? + +## 로그인 인증 과정에서의 어려움 + +상품과 멤버에 대해 컨트롤러와 서비스에서 규칙 없이 작성하였고, 위시 리스트 구현에서 어디서 무엇을 가져와서 어썬 식으로 사용해야할 지 +생각하기에 어려움이 너무 커졌다. + +# 7월 7일 refact 계획 + +1. dto, 컨트롤, 서비스, repository에 대해 통일성 부여하기 + +dto -> 데이터 transfer를 위해 존재 +dto -> 모델로 변환하는 로직은 있으면 좋겠다는 생각 (model은 dto를 몰라도 되도록) +클라이언트 -> 컨트롤러 (dto사용) -> 서비스 (같은 dto 사용??) + +근데 서비스에서는 특정 필드의 값만 필요한 경우가 있다. +서비스 내부에서는 항상 model로 변환해서? 서비스는 여러 모델들의 협업? + +### repository 들 먼저 구조 변경해주기 + +실제 사용되는 String, int 등의 value를 직접 넘겨주도록 한다. +repository는 결국 DB와 매우 큰 밀착관계이고, Model을 몰라도 된다? + +반환은 Model을 해준다? + +- [ ] Repository 통일성 부여 : 기본 검색은 id를 기반으로 한다. Model을 params로 받지 않는다. 반환은 Model로 해준다. \ No newline at end of file diff --git a/src/main/java/gift/controller/WishListController.java b/src/main/java/gift/controller/WishListController.java index e7af5051f..02f1cd762 100644 --- a/src/main/java/gift/controller/WishListController.java +++ b/src/main/java/gift/controller/WishListController.java @@ -7,12 +7,13 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RequestMapping("api/member/{memberid}/wishlist") +@RequestMapping("api/wishlist") @RestController public class WishListController { @@ -23,25 +24,25 @@ public WishListController(WishListService wishListService) { } //상품 리스트 조회 - @GetMapping + @GetMapping("/{memberid}") public WishListDTO getWishList(@PathVariable int memberid, @LoginMember MemberDTO memberDTO) { return wishListService.getWishList(memberid); } //상품 추가 - @PutMapping + @PostMapping("/{memberid}") public void addWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { wishListService.addNewProduct(memberid, wishListDTO.getProductId()); } //상품 삭제 - @DeleteMapping + @DeleteMapping("/{memberid}") public void deleteWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { wishListService.deleteProduct(memberid, wishListDTO.getProductId()); } //상품 수정 - @PutMapping + @PutMapping("/{memberid}") public void updateWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { wishListService.updateProduct(memberid, wishListDTO.getProductId(), wishListDTO.getProductValue()); diff --git a/src/main/java/gift/model/Member.java b/src/main/java/gift/model/Member.java index c475a3cff..d19785e8d 100644 --- a/src/main/java/gift/model/Member.java +++ b/src/main/java/gift/model/Member.java @@ -6,6 +6,7 @@ public class Member { private String email; private String password; private MemberRole role; + private String token; public Member(Long id, String email, String password, MemberRole role) { this.id = id; @@ -45,4 +46,12 @@ public MemberRole getRole() { public void setRole(MemberRole role) { this.role = role; } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } } diff --git a/src/main/java/gift/service/ProductServiceImpl.java b/src/main/java/gift/service/ProductServiceImpl.java index a6facd256..84779255c 100644 --- a/src/main/java/gift/service/ProductServiceImpl.java +++ b/src/main/java/gift/service/ProductServiceImpl.java @@ -3,7 +3,6 @@ import gift.database.JdbcProductRepository; import gift.dto.ProductDTO; import gift.exceptionAdvisor.ProductServiceException; -import gift.model.Product; import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpStatus; @@ -32,10 +31,9 @@ public List readAll() { //새로운 상품 추가 @Override - public void create(ProductDTO prod) { - checkKakao(prod.getName()); - jdbcProductRepository.create( - new Product(null, prod.getName(), prod.getPrice(), prod.getImageUrl())); + public void create(ProductDTO dto) { + checkKakao(dto.getName()); + jdbcProductRepository.create(dto.getName(), dto.getPrice(), dto.getImageUrl()); } diff --git a/src/test/java/gift/controller/WishListControllerTest.java b/src/test/java/gift/controller/WishListControllerTest.java index 9c7a15aa3..bf10633e8 100644 --- a/src/test/java/gift/controller/WishListControllerTest.java +++ b/src/test/java/gift/controller/WishListControllerTest.java @@ -1,6 +1,5 @@ package gift.controller; -import gift.dto.WishListDTO; import gift.model.LoginToken; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -8,10 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.MediaType; import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; -import org.springframework.web.reactive.function.BodyInserters; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class WishListControllerTest { @@ -35,7 +31,7 @@ void addWishList() { LoginToken loginToken = registerAndLogin("test", "123"); //when - addWishListPutRequest(loginToken); + //addWishListPutRequest(loginToken); //then @@ -63,7 +59,7 @@ void deleteWishList() { private LoginToken registerAndLogin(String email, String password) { return null; } - + /* private void addWishListPutRequest(long memberId, WishListDTO wishListDTO) { ResponseSpec responseSpec = webClient.put().uri(uriBuilder -> { return uriBuilder @@ -74,4 +70,6 @@ private void addWishListPutRequest(long memberId, WishListDTO wishListDTO) { }).accept(MediaType.APPLICATION_JSON) .body(BodyInserters.fromValue(wishListDTO)).exchange(); } + + */ } \ No newline at end of file diff --git a/src/test/java/gift/service/MemberServiceTest.java b/src/test/java/gift/service/MemberServiceTest.java new file mode 100644 index 000000000..0de6d9460 --- /dev/null +++ b/src/test/java/gift/service/MemberServiceTest.java @@ -0,0 +1,39 @@ +package gift.service; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import gift.database.JdbcMemeberRepository; +import gift.dto.MemberDTO; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class MemberServiceTest { + + @Autowired + private MemberService memberService; + + @Autowired + private JdbcMemeberRepository jdbcMemeberRepository; + + @Test + @DisplayName("기본 로그인 테스트") + void register() { + + //given + //email과 password를 입력하면, 계정을 생성해준다. + //user role이 null 이면 common으로 설정한다. + String email = "testmail"; + MemberDTO memberDTO = new MemberDTO(email, "abcd", null); + + //when + memberService.register(memberDTO); + + //then + assertNotNull(jdbcMemeberRepository.findByEmail(email)); + + } + +} \ No newline at end of file From 75ccd139e2739432280bdf4fa5fb534e99906c23 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 14:08:46 +0900 Subject: [PATCH 44/53] =?UTF-8?q?fix=20:=20Member=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=86=B5=EA=B3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 정상동작확인 --- history.md | 3 ++- src/main/java/gift/controller/MemberController.java | 8 +++++--- src/main/java/gift/database/JdbcMemeberRepository.java | 2 +- src/main/java/gift/dto/MemberDTO.java | 3 +++ src/test/java/gift/controller/MemberControllerTest.java | 1 - 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/history.md b/history.md index b9ef8aeb1..80f507efe 100644 --- a/history.md +++ b/history.md @@ -75,4 +75,5 @@ repository는 결국 DB와 매우 큰 밀착관계이고, Model을 몰라도 된 반환은 Model을 해준다? -- [ ] Repository 통일성 부여 : 기본 검색은 id를 기반으로 한다. Model을 params로 받지 않는다. 반환은 Model로 해준다. \ No newline at end of file +- [X] Repository 통일성 부여 : 기본 검색은 id를 기반으로 한다. Model을 params로 받지 않는다. 반환은 Model로 해준다. + diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java index b5ea5a66a..da9f3d3e4 100644 --- a/src/main/java/gift/controller/MemberController.java +++ b/src/main/java/gift/controller/MemberController.java @@ -4,6 +4,8 @@ import gift.model.LoginToken; import gift.model.MemberRole; import gift.service.MemberService; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -22,7 +24,7 @@ public MemberController(MemberService memberService) { } @PutMapping - public void register(@RequestBody MemberDTO memberDTO) { + public void register(@RequestBody @Valid MemberDTO memberDTO) { if (memberDTO.getRole() == null) { memberDTO.setRole(MemberRole.COMMON_MEMBER); } @@ -30,8 +32,8 @@ public void register(@RequestBody MemberDTO memberDTO) { } @GetMapping("/login") - public LoginToken login(@RequestParam("email") String email, - @RequestParam("password") String password) { + public LoginToken login(@RequestParam("email") @NotBlank String email, + @RequestParam("password") @NotBlank String password) { return memberService.login(new MemberDTO(email, password, null)); } } diff --git a/src/main/java/gift/database/JdbcMemeberRepository.java b/src/main/java/gift/database/JdbcMemeberRepository.java index f880524bf..207ff1435 100644 --- a/src/main/java/gift/database/JdbcMemeberRepository.java +++ b/src/main/java/gift/database/JdbcMemeberRepository.java @@ -45,7 +45,7 @@ public void create(String email, String password, String role) { public void update(long id, Member member) { String sql = "update member set email=?, password=?, role=?, token=? where id=?"; - template.update(sql, member.getEmail(), member.getPassword(), member.getRole(), + template.update(sql, member.getEmail(), member.getPassword(), member.getRole().toString(), member.getToken(), id); } diff --git a/src/main/java/gift/dto/MemberDTO.java b/src/main/java/gift/dto/MemberDTO.java index 3f83e6e6f..8d6673d10 100644 --- a/src/main/java/gift/dto/MemberDTO.java +++ b/src/main/java/gift/dto/MemberDTO.java @@ -1,11 +1,14 @@ package gift.dto; import gift.model.MemberRole; +import jakarta.validation.constraints.NotBlank; public class MemberDTO { private Long id; + @NotBlank(message = "이메일을 입력해주세요") private String email; + @NotBlank(message = "비밀번호를 입력해주세요") private String password; private MemberRole role; diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java index b752ab422..e54e0477c 100644 --- a/src/test/java/gift/controller/MemberControllerTest.java +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -56,7 +56,6 @@ void registerDuplicateEmail() { ResponseSpec responseSpec = registerMemberPutRequest(dto); ResponseSpec responseSpec2 = registerMemberPutRequest(dto2); - responseSpec.expectStatus().isOk(); responseSpec2.expectStatus().isForbidden(); } From b8f64546434ae6e4b826291b233d5ac4327be1be Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Sun, 7 Jul 2024 23:34:25 +0900 Subject: [PATCH 45/53] =?UTF-8?q?refact=20:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/{ExceptionResponseDTO.java => ExceptionResponse.java} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/main/java/gift/dto/{ExceptionResponseDTO.java => ExceptionResponse.java} (63%) diff --git a/src/main/java/gift/dto/ExceptionResponseDTO.java b/src/main/java/gift/dto/ExceptionResponse.java similarity index 63% rename from src/main/java/gift/dto/ExceptionResponseDTO.java rename to src/main/java/gift/dto/ExceptionResponse.java index 423195e6a..1a9021905 100644 --- a/src/main/java/gift/dto/ExceptionResponseDTO.java +++ b/src/main/java/gift/dto/ExceptionResponse.java @@ -1,9 +1,9 @@ package gift.dto; -public class ExceptionResponseDTO { +public class ExceptionResponse { private String message; - public ExceptionResponseDTO(String message) { + public ExceptionResponse(String message) { this.message = message; } public String getMessage() { From f5787ddf40211c1e45c6737e516859a4ee1be5f2 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 17:22:36 +0900 Subject: [PATCH 46/53] =?UTF-8?q?refact=20:=20PR=20=EB=B0=98=EC=98=81=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 불필요 주석 삭제 2. restful 한 api url 설정 3. camelCase 로 변경 4. 데이터베이스 unique 설정 5. 네이밍단순화 (addNew -> add) --- history.md | 24 ++++++++++++++++++ .../gift/ArgumentResolver/LoginMember.java | 2 +- .../gift/controller/AdminPageController.java | 6 ++--- .../gift/controller/ProductController.java | 10 ++++---- .../gift/controller/WishListController.java | 25 +++++++++---------- .../gift/database/JdbcMemeberRepository.java | 2 +- .../ProductExceptionAdvisor.java | 14 +++++------ src/main/java/gift/service/MemberService.java | 9 +++---- .../java/gift/service/ProductService.java | 11 -------- .../java/gift/service/ProductServiceImpl.java | 9 ++----- .../java/gift/service/WishListService.java | 2 +- .../gift/service/WishListServiceImpl.java | 2 +- 12 files changed, 60 insertions(+), 56 deletions(-) diff --git a/history.md b/history.md index 80f507efe..2992dab22 100644 --- a/history.md +++ b/history.md @@ -77,3 +77,27 @@ repository는 결국 DB와 매우 큰 밀착관계이고, Model을 몰라도 된 - [X] Repository 통일성 부여 : 기본 검색은 id를 기반으로 한다. Model을 params로 받지 않는다. 반환은 Model로 해준다. +# 7월 8일 (월) + +1. 피드백에 따라서 수정 진행 +2. wishlist 완벽하게 동작하도록 수정하기 + +## 피드백 반영 + +1. 공통적인 포멧이 있는 것은 좋지만, DTO는 용도에 맞게 사용하는 것이 좋다. 불필요한 데이터까지 내줄 필요 없음 + +2. 문서는 코드 자체로 잘 이해할 수 있도록 해두는 것이 좋다. -> 클린코드 + +3. 불필요한 축약어 사용 X, stream 적극적인 활용 -> ok + +4. 파일 전반적인 설명 주석은 상단에, 그리고 주석이 필요하다면 자세하게 -> ok + +5. rest 규격에 맞게 경로 설계하기 -> {id} 등으로 받기 -> ok + +6. 변수명 camelCase 로 -> ok + +7. 인증 토큰은 저장할 필요가 있는가? + +8. 중복된 이메일 -> 데이터베이스에서도 막을 수 있도록 유니크 설정시켜주기 -> ok + +9. addNew 와 같이 중복된 네이밍 X diff --git a/src/main/java/gift/ArgumentResolver/LoginMember.java b/src/main/java/gift/ArgumentResolver/LoginMember.java index e010e9406..5b82d0dcd 100644 --- a/src/main/java/gift/ArgumentResolver/LoginMember.java +++ b/src/main/java/gift/ArgumentResolver/LoginMember.java @@ -8,5 +8,5 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface LoginMember { - //컨트롤러에서 사용을 위해 + } diff --git a/src/main/java/gift/controller/AdminPageController.java b/src/main/java/gift/controller/AdminPageController.java index fbd52896a..09b4d1cb1 100644 --- a/src/main/java/gift/controller/AdminPageController.java +++ b/src/main/java/gift/controller/AdminPageController.java @@ -27,12 +27,12 @@ public AdminPageController(ProductService productService) { public String adminPage(Model model) { model.addAttribute("products", productService.readAll()); model.addAttribute("productDTO", new ProductDTO()); - return "admin/index";//렌더링하는 html 이름 + return "admin/index"; } - @PostMapping //admin으로 오는 post에 대해서 submit + @PostMapping public String adminPageSubmit(@ModelAttribute("productDTO") @Valid ProductDTO productDTO) { - productService.create(productDTO); //서비스에 접근해서 해당 부분을 추가해주도록 한다. + productService.create(productDTO); return "redirect:/admin/products"; } diff --git a/src/main/java/gift/controller/ProductController.java b/src/main/java/gift/controller/ProductController.java index c1dc523aa..720c3c4b8 100644 --- a/src/main/java/gift/controller/ProductController.java +++ b/src/main/java/gift/controller/ProductController.java @@ -6,11 +6,11 @@ import java.util.List; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -55,16 +55,16 @@ public void add(@RequestBody @Valid ProductDTO dto) { * @param id 수정하고자 하는 상품의 id * @param dto 수정하고자 하는 값 이외 null로 지정 */ - @PutMapping("/products") - public void update(@RequestParam("id") Long id, @RequestBody @Valid ProductDTO dto) { + @PutMapping("/products/{id}") + public void update(@PathVariable Long id, @RequestBody @Valid ProductDTO dto) { if (id == null) { throw new IllegalArgumentException("id를 입력해주세요"); } changeCheckAndUpdate(id, dto); } - @DeleteMapping("/products") - public void delete(@RequestParam("id") Long id) { + @DeleteMapping("/products/{id}") + public void delete(@PathVariable Long id) { productService.delete(id); } diff --git a/src/main/java/gift/controller/WishListController.java b/src/main/java/gift/controller/WishListController.java index 02f1cd762..95dd11229 100644 --- a/src/main/java/gift/controller/WishListController.java +++ b/src/main/java/gift/controller/WishListController.java @@ -23,28 +23,27 @@ public WishListController(WishListService wishListService) { this.wishListService = wishListService; } - //상품 리스트 조회 - @GetMapping("/{memberid}") - public WishListDTO getWishList(@PathVariable int memberid, @LoginMember MemberDTO memberDTO) { - return wishListService.getWishList(memberid); + @GetMapping("/{memberId}") + public WishListDTO getWishList(@PathVariable int memberId, @LoginMember MemberDTO memberDTO) { + return wishListService.getWishList(memberId); } //상품 추가 - @PostMapping("/{memberid}") - public void addWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { - wishListService.addNewProduct(memberid, wishListDTO.getProductId()); + @PostMapping("/{memberId}") + public void addWishList(@PathVariable int memberId, @RequestBody WishListDTO wishListDTO) { + wishListService.addProduct(memberId, wishListDTO.getProductId()); } //상품 삭제 - @DeleteMapping("/{memberid}") - public void deleteWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { - wishListService.deleteProduct(memberid, wishListDTO.getProductId()); + @DeleteMapping("/{memberId}") + public void deleteWishList(@PathVariable int memberId, @RequestBody WishListDTO wishListDTO) { + wishListService.deleteProduct(memberId, wishListDTO.getProductId()); } //상품 수정 - @PutMapping("/{memberid}") - public void updateWishList(@PathVariable int memberid, @RequestBody WishListDTO wishListDTO) { - wishListService.updateProduct(memberid, wishListDTO.getProductId(), + @PutMapping("/{memberId}") + public void updateWishList(@PathVariable int memberId, @RequestBody WishListDTO wishListDTO) { + wishListService.updateProduct(memberId, wishListDTO.getProductId(), wishListDTO.getProductValue()); } diff --git a/src/main/java/gift/database/JdbcMemeberRepository.java b/src/main/java/gift/database/JdbcMemeberRepository.java index 207ff1435..67f6d7a25 100644 --- a/src/main/java/gift/database/JdbcMemeberRepository.java +++ b/src/main/java/gift/database/JdbcMemeberRepository.java @@ -24,7 +24,7 @@ private void createTable() { template.update( "create table if not exists member(" + "id long primary key auto_increment, " - + "email varchar(255) not null, " + + "email varchar(255) unique not null, " + "password varchar(255) not null, " + "role varchar(255) not null, " + "token varchar(255))"); diff --git a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java index b5f9d74a2..907210c24 100644 --- a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java +++ b/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java @@ -1,7 +1,7 @@ package gift.exceptionAdvisor; -import gift.dto.ExceptionResponseDTO; +import gift.dto.ExceptionResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -13,24 +13,24 @@ public class ProductExceptionAdvisor { @ExceptionHandler(MethodArgumentNotValidException.class) //유효성 검사 실패 시 - public ResponseEntity productValidationException( + public ResponseEntity productValidationException( MethodArgumentNotValidException exception) { - return new ResponseEntity<>(new ExceptionResponseDTO( + return new ResponseEntity<>(new ExceptionResponse( exception.getBindingResult().getFieldError().getDefaultMessage()), exception.getStatusCode()); } @ExceptionHandler(ProductServiceException.class) - public ResponseEntity productServiceException( + public ResponseEntity productServiceException( ProductServiceException exception) { - return new ResponseEntity<>(new ExceptionResponseDTO(exception.getMessage()), + return new ResponseEntity<>(new ExceptionResponse(exception.getMessage()), exception.getStatusCode()); } @ExceptionHandler(MemberServiceException.class) - public ResponseEntity memberServiceException( + public ResponseEntity memberServiceException( MemberServiceException exception) { - return new ResponseEntity<>(new ExceptionResponseDTO(exception.getMessage()), + return new ResponseEntity<>(new ExceptionResponse(exception.getMessage()), exception.getStatusCode()); } } diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index 6a561d851..64ae30e9e 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -5,14 +5,11 @@ public interface MemberService { - //회원가입을 진행한다. - public void register(MemberDTO memberDTO); + void register(MemberDTO memberDTO); - //로그인을 진행한다. - public LoginToken login(MemberDTO memberDTO); + LoginToken login(MemberDTO memberDTO); - //회원의 접근 권한을 확인한다. - public boolean checkRole(LoginToken loginToken); + boolean checkRole(LoginToken loginToken); MemberDTO getLoginUser(String token); } diff --git a/src/main/java/gift/service/ProductService.java b/src/main/java/gift/service/ProductService.java index 432c2bb7d..63c5409b6 100644 --- a/src/main/java/gift/service/ProductService.java +++ b/src/main/java/gift/service/ProductService.java @@ -5,27 +5,16 @@ public interface ProductService { - /* - 상품 수정 시 만약 문제가 생기면 어떤 값에 의해서 생기는 지 파악의 어려움을 막기 위해 - update를 각 항목마다 분리 - */ - - //상품 리스트 전체 조회 List readAll(); - //새 상품 생성 void create(ProductDTO prod); - //상품 이름 변경 void updateName(long id, String name); - //상품 가격 변경 void updatePrice(long id, int price); - //상품 이미지 변경 void updateImageUrl(long id, String url); - //상품 삭제 void delete(long id); } diff --git a/src/main/java/gift/service/ProductServiceImpl.java b/src/main/java/gift/service/ProductServiceImpl.java index 84779255c..41295a441 100644 --- a/src/main/java/gift/service/ProductServiceImpl.java +++ b/src/main/java/gift/service/ProductServiceImpl.java @@ -3,7 +3,6 @@ import gift.database.JdbcProductRepository; import gift.dto.ProductDTO; import gift.exceptionAdvisor.ProductServiceException; -import java.util.ArrayList; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -20,13 +19,9 @@ public ProductServiceImpl(JdbcProductRepository jdbcProductRepository) { @Override public List readAll() { var products = jdbcProductRepository.findAll(); - List productDTOList = new ArrayList<>(); - for (var product : products) { //DTO로 전환 - productDTOList.add(new ProductDTO(product)); - } - - return productDTOList; + return products.stream().map(product -> new ProductDTO(product.getId(), product.getName(), + product.getPrice(), product.getImageUrl())).toList(); } //새로운 상품 추가 diff --git a/src/main/java/gift/service/WishListService.java b/src/main/java/gift/service/WishListService.java index 9bc5e0c70..8a9b6c9e9 100644 --- a/src/main/java/gift/service/WishListService.java +++ b/src/main/java/gift/service/WishListService.java @@ -4,7 +4,7 @@ public interface WishListService { - void addNewProduct(long memberId, long productId); + void addProduct(long memberId, long productId); void deleteProduct(long memberId, long productId); diff --git a/src/main/java/gift/service/WishListServiceImpl.java b/src/main/java/gift/service/WishListServiceImpl.java index a495905dd..205e1db26 100644 --- a/src/main/java/gift/service/WishListServiceImpl.java +++ b/src/main/java/gift/service/WishListServiceImpl.java @@ -16,7 +16,7 @@ public WishListServiceImpl(JdbcWishListRepository jdbcWishListRepository) { } @Override - public void addNewProduct(long memberId, long productId) { + public void addProduct(long memberId, long productId) { WishList wishList = new WishList(null, memberId, new HashMap<>()); wishList.updateProduct(productId, 1); jdbcWishListRepository.insertWishList(wishList); From 9f5fefb8d8b514bea8a9e99905cc24e9e4ab54f3 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 18:10:01 +0900 Subject: [PATCH 47/53] =?UTF-8?q?refact=20:=20=ED=81=B4=EB=9E=98=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD,=20=EA=B0=84?= =?UTF-8?q?=EB=8B=A8=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...xceptionAdvisor.java => ExceptionAdvisor.java} | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) rename src/main/java/gift/exceptionAdvisor/{ProductExceptionAdvisor.java => ExceptionAdvisor.java} (80%) diff --git a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java b/src/main/java/gift/exceptionAdvisor/ExceptionAdvisor.java similarity index 80% rename from src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java rename to src/main/java/gift/exceptionAdvisor/ExceptionAdvisor.java index 907210c24..fc7674a96 100644 --- a/src/main/java/gift/exceptionAdvisor/ProductExceptionAdvisor.java +++ b/src/main/java/gift/exceptionAdvisor/ExceptionAdvisor.java @@ -10,9 +10,12 @@ @ControllerAdvice @RestController -public class ProductExceptionAdvisor { +public class ExceptionAdvisor { - @ExceptionHandler(MethodArgumentNotValidException.class) //유효성 검사 실패 시 + /* + ProductController 유효성 검사 실패 핸들러 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity productValidationException( MethodArgumentNotValidException exception) { return new ResponseEntity<>(new ExceptionResponse( @@ -20,6 +23,10 @@ public ResponseEntity productValidationException( exception.getStatusCode()); } + /* + ProductService 예외 핸들러 + 금지된 문구 사용 etc + */ @ExceptionHandler(ProductServiceException.class) public ResponseEntity productServiceException( ProductServiceException exception) { @@ -27,6 +34,10 @@ public ResponseEntity productServiceException( exception.getStatusCode()); } + /* + MemberService 예외 핸들러 + 이메일 중복 등 + */ @ExceptionHandler(MemberServiceException.class) public ResponseEntity memberServiceException( MemberServiceException exception) { From d09d4ff406a68bf3e158569704fbb26604edca0b Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 18:10:30 +0900 Subject: [PATCH 48/53] =?UTF-8?q?feat=20:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EB=B6=88=ED=95=84=EC=9A=94=20=EC=A3=BC=EC=84=9D?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/gift/service/MemberServiceImpl.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/gift/service/MemberServiceImpl.java b/src/main/java/gift/service/MemberServiceImpl.java index cfd51801f..b993c958b 100644 --- a/src/main/java/gift/service/MemberServiceImpl.java +++ b/src/main/java/gift/service/MemberServiceImpl.java @@ -31,7 +31,7 @@ public void register(MemberDTO memberDTO) { @Override public LoginToken login(MemberDTO memberDTO) { - Member member = jdbcMemeberRepository.findByEmail(memberDTO.getEmail()); + Member member = findByEmail(memberDTO.getEmail()); if (memberDTO.getPassword().equals(member.getPassword())) { LoginToken loginToken = new LoginToken(member.getId(), member.getEmail(), @@ -57,12 +57,6 @@ public MemberDTO getLoginUser(String token) { } - /** - * 이메일 중복 확인 - * - * @param email - * @return 중복된 이메일인 경우 true 반환 - */ private boolean checkEmailDuplication(String email) { try { jdbcMemeberRepository.findByEmail(email); @@ -71,4 +65,12 @@ private boolean checkEmailDuplication(String email) { return false; } } + + private Member findByEmail(String email) { + try { + return jdbcMemeberRepository.findByEmail(email); + } catch (EmptyResultDataAccessException e) { + throw new MemberServiceException("잘못된 로그인 시도입니다.", HttpStatus.FORBIDDEN); + } + } } From 309efa530458c0f09d85ba6b3a8edf8ab5472d6e Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 18:10:50 +0900 Subject: [PATCH 49/53] =?UTF-8?q?test=20:=20=EC=97=86=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=EB=A1=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=8B=9C=EB=8F=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/MemberControllerTest.java | 54 ++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java index e54e0477c..2c287f1b6 100644 --- a/src/test/java/gift/controller/MemberControllerTest.java +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.MediaType; @@ -19,7 +18,6 @@ class MemberControllerTest { @LocalServerPort private int port; private String baseUrl; - @Autowired private WebTestClient webClient; @BeforeEach @@ -32,7 +30,7 @@ void setUp() { @DisplayName("회원가입 성공") void registerMember() { //given - String email = "abcd@gmail.com"; + String email = "asdef@gmail.com"; String password = "abcd"; MemberDTO dto = new MemberDTO(email, password, null); @@ -53,7 +51,7 @@ void registerDuplicateEmail() { MemberDTO dto2 = new MemberDTO(email, "4567", null); //when - ResponseSpec responseSpec = registerMemberPutRequest(dto); + registerMemberPutRequest(dto); ResponseSpec responseSpec2 = registerMemberPutRequest(dto2); responseSpec2.expectStatus().isForbidden(); @@ -93,29 +91,61 @@ void login() { String email = "abcd@gmail.com"; String password = "abcd"; MemberDTO dto = new MemberDTO(email, password, null); - ResponseSpec responseSpec = registerMemberPutRequest(dto); + registerMemberPutRequest(dto); //when - ResponseSpec responseSeec = webClient.get().uri(uriBuilder -> uriBuilder + ResponseSpec responseSpec = webClient.get().uri(uriBuilder -> uriBuilder .path("/api/member/login") .queryParam("email", dto.getEmail()) .queryParam("password", dto.getPassword()) .build()).accept(MediaType.APPLICATION_JSON).exchange(); //then - responseSeec.expectStatus().isOk(); - responseSeec.expectBody(LoginToken.class); + responseSpec.expectStatus().isOk(); + responseSpec.expectBody(LoginToken.class); } - /* + @Test @DisplayName("패스워드가 불일치한 경우") + void wrongPassword() { + //given + String email = "abcd@gmail.com"; + String password = "abcd"; + String wrongPassword = "wrong"; + MemberDTO dto = new MemberDTO(email, password, null); + registerMemberPutRequest(dto); + + //when + ResponseSpec responseSpec = webClient.get().uri(uriBuilder -> uriBuilder + .path("/api/member/login") + .queryParam("email", dto.getEmail()) + .queryParam("password", wrongPassword) + .build()).accept(MediaType.APPLICATION_JSON).exchange(); + + //then + responseSpec.expectStatus().isForbidden(); + + } @Test @DisplayName("회원가입이 되지않은 이메일로 로그인을 시도하는 경우") + void noRegister() { + //given + String email = "imno@gmail.com"; + String password = "abcd"; + MemberDTO dto = new MemberDTO(email, password, null); + + //when + ResponseSpec responseSpec = webClient.get().uri(uriBuilder -> uriBuilder + .path("/api/member/login") + .queryParam("email", dto.getEmail()) + .queryParam("password", dto.getPassword()) + .build()).accept(MediaType.APPLICATION_JSON).exchange(); + + //then + responseSpec.expectStatus().isForbidden(); + } - @Test - @DisplayName("로그인 성공으로 Token을 받아오기 성공") - */ private ResponseSpec registerMemberPutRequest(MemberDTO dto) { ResponseSpec responseSpec = webClient.put().uri("/api/member") From 59eaa346167b9b01b64eeaf5cb7e0aa4bb925e86 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 18:57:53 +0900 Subject: [PATCH 50/53] =?UTF-8?q?feat=20:=20controller=20test=20helper=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit httpmethod 편하게 생성, 검증을 위해 --- .../gift/controller/WebTestClientHelper.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/test/java/gift/controller/WebTestClientHelper.java diff --git a/src/test/java/gift/controller/WebTestClientHelper.java b/src/test/java/gift/controller/WebTestClientHelper.java new file mode 100644 index 000000000..80d2128bd --- /dev/null +++ b/src/test/java/gift/controller/WebTestClientHelper.java @@ -0,0 +1,88 @@ +package gift.controller; + +import jakarta.validation.constraints.NotNull; +import java.util.Map; +import org.springframework.http.MediaType; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.util.UriComponentsBuilder; + +/** + * webflux 를 이용한 webTestClient를 편하게 사용할 수 있습니다.
build.gradle 추가-> implementation + * 'org.springframework.boot:spring-boot-starter-webflux'
(@SpringBootTest) 에서 Rest api test에 + * 사용할 수 있습니다.
url에 매개변수를 삽입 : uriMakeUseParameters로 url 생성
+ *

+ * 사용예시
+ *

{@code
+ *     @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+ *     class ControllerTest {
+ *
+ *     @LocalServerPort
+ *     private int port;
+ *     WebTestClientHelper webClient;
+ *
+ *     @BeforeEach
+ *     void setUp() {
+ *         webClient = new WebTestClientHelper(port);
+ *     }
+ *
+ *     @Test
+ *     @DisplayName("WebTestClientHelper 정상 작동 확인")
+ *     void isOk() {
+ *         //given
+ *         String url = "/admin/products";
+ *
+ *         //when
+ *         var response = webClient.get(url);
+ *
+ *         //then
+ *         response.expectStatus().isOk();
+ *         response.expectHeader().contentType("application/json");
+ *         response.expectBody().json(...)
+ *
+ *     }
+ *  }
+ */ +public class WebTestClientHelper { + + private final WebTestClient webTestClient; + + public WebTestClientHelper(int port) { + this.webTestClient = WebTestClient.bindToServer().baseUrl("http://localhost:" + port) + .build(); + } + + public ResponseSpec get(String url) { + return webTestClient.get().uri(url).accept(MediaType.APPLICATION_JSON).exchange(); + } + + public ResponseSpec post(String url, Object body) { + return webTestClient.post().uri(url).accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(body)).exchange(); + } + + public ResponseSpec put(String url, Object body) { + return webTestClient.put().uri(url).accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(body)).exchange(); + } + + public ResponseSpec delete(String url) { + return webTestClient.delete().uri(url).accept(MediaType.APPLICATION_JSON).exchange(); + } + + /** + * url을 만들어 준다. + * + * @param path 기본경로 ex)/api/products + * @param parameters url 매개변수 ex) [["email" = "happy"],["password" = "1234"]] + * @return String type 생성된 uri + */ + public String uriMakeUseParameters(@NotNull String path, Map parameters) { + UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); + builder.path(path); + parameters.forEach(builder::queryParam); + return builder.build().toString(); + + } +} From 3b827c218c3c25dd3b0d2e2c6312ee2ec93c02a2 Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 22:06:33 +0900 Subject: [PATCH 51/53] =?UTF-8?q?refact=20:=20token=20=EA=B8=B0=EB=B0=98?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=9D=B8=EC=A6=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/MemberController.java | 4 +- .../gift/database/JdbcMemeberRepository.java | 12 +--- src/main/java/gift/dto/LoginMemberToken.java | 21 ++++++ src/main/java/gift/model/LoginToken.java | 66 ------------------- .../java/gift/service/AuthenticationTool.java | 37 +++++++++++ src/main/java/gift/service/MemberService.java | 6 +- .../java/gift/service/MemberServiceImpl.java | 25 +++---- .../gift/controller/MemberControllerTest.java | 4 +- .../controller/WishListControllerTest.java | 26 +++----- 9 files changed, 91 insertions(+), 110 deletions(-) create mode 100644 src/main/java/gift/dto/LoginMemberToken.java delete mode 100644 src/main/java/gift/model/LoginToken.java create mode 100644 src/main/java/gift/service/AuthenticationTool.java diff --git a/src/main/java/gift/controller/MemberController.java b/src/main/java/gift/controller/MemberController.java index da9f3d3e4..451a6ab65 100644 --- a/src/main/java/gift/controller/MemberController.java +++ b/src/main/java/gift/controller/MemberController.java @@ -1,7 +1,7 @@ package gift.controller; +import gift.dto.LoginMemberToken; import gift.dto.MemberDTO; -import gift.model.LoginToken; import gift.model.MemberRole; import gift.service.MemberService; import jakarta.validation.Valid; @@ -32,7 +32,7 @@ public void register(@RequestBody @Valid MemberDTO memberDTO) { } @GetMapping("/login") - public LoginToken login(@RequestParam("email") @NotBlank String email, + public LoginMemberToken login(@RequestParam("email") @NotBlank String email, @RequestParam("password") @NotBlank String password) { return memberService.login(new MemberDTO(email, password, null)); } diff --git a/src/main/java/gift/database/JdbcMemeberRepository.java b/src/main/java/gift/database/JdbcMemeberRepository.java index 67f6d7a25..6563b79f4 100644 --- a/src/main/java/gift/database/JdbcMemeberRepository.java +++ b/src/main/java/gift/database/JdbcMemeberRepository.java @@ -26,8 +26,7 @@ private void createTable() { + "id long primary key auto_increment, " + "email varchar(255) unique not null, " + "password varchar(255) not null, " - + "role varchar(255) not null, " - + "token varchar(255))"); + + "role varchar(255) not null)"); } @@ -44,9 +43,9 @@ public void create(String email, String password, String role) { } public void update(long id, Member member) { - String sql = "update member set email=?, password=?, role=?, token=? where id=?"; + String sql = "update member set email=?, password=?, role=? where id=?"; template.update(sql, member.getEmail(), member.getPassword(), member.getRole().toString(), - member.getToken(), id); + id); } public void delete(long id) { @@ -64,11 +63,6 @@ public Member findByEmail(String email) { return template.queryForObject(sql, memberRowMapper(), email); } - public Member findByToken(String token) { - String sql = "select * from member where token = ?"; - return template.queryForObject(sql, memberRowMapper(), token); - } - private RowMapper memberRowMapper() { return (rs, rowNum) -> new Member( rs.getLong("id"), diff --git a/src/main/java/gift/dto/LoginMemberToken.java b/src/main/java/gift/dto/LoginMemberToken.java new file mode 100644 index 000000000..63e4a7069 --- /dev/null +++ b/src/main/java/gift/dto/LoginMemberToken.java @@ -0,0 +1,21 @@ +package gift.dto; + +public class LoginMemberToken { + + private String token; + + public LoginMemberToken() { + } + + public LoginMemberToken(String token) { + this.token = token; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } +} diff --git a/src/main/java/gift/model/LoginToken.java b/src/main/java/gift/model/LoginToken.java deleted file mode 100644 index ee7dbcdb5..000000000 --- a/src/main/java/gift/model/LoginToken.java +++ /dev/null @@ -1,66 +0,0 @@ -package gift.model; - -import io.jsonwebtoken.Jwts.SIG; -import java.security.Key; -import java.util.Objects; - -public class LoginToken { - - private String token; - - private Key key = SIG.HS256.key().build(); - - private String email; - private String role; - private Long memberId; - - public LoginToken() { - } - - public LoginToken(Long memberId, String email, MemberRole role) { - this.token = email + ":" + role.toString(); - /* JWT 학습 이후 구현하기 - this.token = Jwts.builder() - .setSubject(email) - .claim("role", role.toString()) - .signWith(key) - .compact(); - - */ - this.email = email; - this.role = role.toString(); - } - - public String getToken() { - return token; - } - - public Long getMemberId() { - if (token != null) { - return memberId; - } - //JwtParser parser = Jwts.parser().build(); - return null; - - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - LoginToken that = (LoginToken) o; - return Objects.equals(token, that.token); - } - - @Override - public int hashCode() { - return Objects.hashCode(token); - } - - -} diff --git a/src/main/java/gift/service/AuthenticationTool.java b/src/main/java/gift/service/AuthenticationTool.java new file mode 100644 index 000000000..104e6e097 --- /dev/null +++ b/src/main/java/gift/service/AuthenticationTool.java @@ -0,0 +1,37 @@ +package gift.service; + +import gift.exceptionAdvisor.MemberServiceException; +import gift.model.Member; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.Jwts.SIG; +import javax.crypto.SecretKey; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +public class AuthenticationTool { + + private final SecretKey key = SIG.HS256.key().build(); + + public AuthenticationTool() { + } + + public String makeToken(Member member) { + return Jwts.builder().claim("id", member.getId()) + .signWith(key).toString(); + } + + public long parseToken(String token) { + try { + var claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(token) + .getPayload(); + return Long.parseLong(claims.get("id").toString()); + } catch (JwtException e) { + throw new MemberServiceException("JWT 인증 실패", HttpStatus.FORBIDDEN); + } + + } + + +} diff --git a/src/main/java/gift/service/MemberService.java b/src/main/java/gift/service/MemberService.java index 64ae30e9e..21d8dee99 100644 --- a/src/main/java/gift/service/MemberService.java +++ b/src/main/java/gift/service/MemberService.java @@ -1,15 +1,15 @@ package gift.service; +import gift.dto.LoginMemberToken; import gift.dto.MemberDTO; -import gift.model.LoginToken; public interface MemberService { void register(MemberDTO memberDTO); - LoginToken login(MemberDTO memberDTO); + LoginMemberToken login(MemberDTO memberDTO); - boolean checkRole(LoginToken loginToken); + boolean checkRole(MemberDTO memberDTO); MemberDTO getLoginUser(String token); } diff --git a/src/main/java/gift/service/MemberServiceImpl.java b/src/main/java/gift/service/MemberServiceImpl.java index b993c958b..1e554a518 100644 --- a/src/main/java/gift/service/MemberServiceImpl.java +++ b/src/main/java/gift/service/MemberServiceImpl.java @@ -1,9 +1,9 @@ package gift.service; import gift.database.JdbcMemeberRepository; +import gift.dto.LoginMemberToken; import gift.dto.MemberDTO; import gift.exceptionAdvisor.MemberServiceException; -import gift.model.LoginToken; import gift.model.Member; import gift.model.MemberRole; import org.springframework.dao.EmptyResultDataAccessException; @@ -15,8 +15,12 @@ public class MemberServiceImpl implements MemberService { private JdbcMemeberRepository jdbcMemeberRepository; - public MemberServiceImpl(JdbcMemeberRepository jdbcMemeberRepository) { + private AuthenticationTool authenticationTool; + + public MemberServiceImpl(JdbcMemeberRepository jdbcMemeberRepository, + AuthenticationTool authenticationTool) { this.jdbcMemeberRepository = jdbcMemeberRepository; + this.authenticationTool = authenticationTool; } @Override @@ -30,30 +34,27 @@ public void register(MemberDTO memberDTO) { } @Override - public LoginToken login(MemberDTO memberDTO) { + public LoginMemberToken login(MemberDTO memberDTO) { Member member = findByEmail(memberDTO.getEmail()); if (memberDTO.getPassword().equals(member.getPassword())) { - LoginToken loginToken = new LoginToken(member.getId(), member.getEmail(), - member.getRole()); - member.setToken(loginToken.getToken()); - jdbcMemeberRepository.update(member.getId(), member); - return loginToken; + String token = authenticationTool.makeToken(member); + return new LoginMemberToken(token); } throw new MemberServiceException("잘못된 로그인 시도입니다.", HttpStatus.FORBIDDEN); } @Override - public boolean checkRole(LoginToken loginToken) { + public boolean checkRole(MemberDTO memberDTO) { return false; } @Override public MemberDTO getLoginUser(String token) { - Member member = jdbcMemeberRepository.findByToken(token); - - return new MemberDTO(member.getEmail(), null, member.getRole()); + long id = authenticationTool.parseToken(token); + Member member = jdbcMemeberRepository.findById(id); + return new MemberDTO(member.getEmail(), member.getPassword(), member.getRole()); } diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java index 2c287f1b6..21fdc4e50 100644 --- a/src/test/java/gift/controller/MemberControllerTest.java +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -1,7 +1,7 @@ package gift.controller; import gift.dto.MemberDTO; -import gift.model.LoginToken; +import gift.service.AuthenticationTool; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -102,7 +102,7 @@ void login() { //then responseSpec.expectStatus().isOk(); - responseSpec.expectBody(LoginToken.class); + responseSpec.expectBody(AuthenticationTool.class); } @Test diff --git a/src/test/java/gift/controller/WishListControllerTest.java b/src/test/java/gift/controller/WishListControllerTest.java index bf10633e8..2125c6c39 100644 --- a/src/test/java/gift/controller/WishListControllerTest.java +++ b/src/test/java/gift/controller/WishListControllerTest.java @@ -1,39 +1,33 @@ package gift.controller; -import gift.model.LoginToken; +import gift.service.AuthenticationTool; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.test.web.reactive.server.WebTestClient; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class WishListControllerTest { @LocalServerPort private int port; - private String baseUrl; - @Autowired - private WebTestClient webClient; + WebTestClientHelper webClient; @BeforeEach void setUp() { - baseUrl = "http://localhost:" + port; - webClient = WebTestClient.bindToServer().baseUrl(baseUrl).build(); + webClient = new WebTestClientHelper(port); } @Test - @DisplayName("위시 리스트 추가") - void addWishList() { - //given - LoginToken loginToken = registerAndLogin("test", "123"); + @DisplayName("WebTestClientHelper 정상 작동 확인") + void isOk() { - //when - //addWishListPutRequest(loginToken); + var response = webClient.get("/admin/products"); - //then + response.expectStatus().isOk(); + response.expectHeader().contentType("application/json"); + //response.expectBody().json(...) } @@ -56,7 +50,7 @@ void deleteWishList() { } - private LoginToken registerAndLogin(String email, String password) { + private AuthenticationTool registerAndLogin(String email, String password) { return null; } /* From 8e77209e795bd02a2addce0293f1974be38eee0d Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 23:39:26 +0900 Subject: [PATCH 52/53] =?UTF-8?q?refact=20:=20wishlist=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gift/controller/WishListController.java | 30 ++++++++++--------- .../gift/database/JdbcWishListRepository.java | 2 +- src/main/java/gift/dto/MemberDTO.java | 10 +++++++ src/main/java/gift/dto/WishListDTO.java | 9 ++++++ .../java/gift/service/AuthenticationTool.java | 4 +-- .../java/gift/service/WishListService.java | 3 +- .../gift/service/WishListServiceImpl.java | 9 ++++-- .../gift/controller/MemberControllerTest.java | 4 +-- .../gift/controller/WebTestClientHelper.java | 4 +++ 9 files changed, 53 insertions(+), 22 deletions(-) diff --git a/src/main/java/gift/controller/WishListController.java b/src/main/java/gift/controller/WishListController.java index 95dd11229..a9626fdff 100644 --- a/src/main/java/gift/controller/WishListController.java +++ b/src/main/java/gift/controller/WishListController.java @@ -4,16 +4,16 @@ import gift.dto.MemberDTO; import gift.dto.WishListDTO; import gift.service.WishListService; +import java.util.List; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -@RequestMapping("api/wishlist") +@RequestMapping("/api/wishlist") @RestController public class WishListController { @@ -23,27 +23,29 @@ public WishListController(WishListService wishListService) { this.wishListService = wishListService; } - @GetMapping("/{memberId}") - public WishListDTO getWishList(@PathVariable int memberId, @LoginMember MemberDTO memberDTO) { - return wishListService.getWishList(memberId); + @GetMapping + public List getWishList(@LoginMember MemberDTO memberDTO) { + return wishListService.getWishList(memberDTO.getId()); } //상품 추가 - @PostMapping("/{memberId}") - public void addWishList(@PathVariable int memberId, @RequestBody WishListDTO wishListDTO) { - wishListService.addProduct(memberId, wishListDTO.getProductId()); + @PostMapping + public void addWishList(@LoginMember MemberDTO memberDTO, + @RequestBody WishListDTO wishListDTO) { + //wishListDTO.setMemberId(memberDTO.getId()); + wishListService.addProduct(wishListDTO.getMemberId(), wishListDTO.getProductId()); } //상품 삭제 - @DeleteMapping("/{memberId}") - public void deleteWishList(@PathVariable int memberId, @RequestBody WishListDTO wishListDTO) { - wishListService.deleteProduct(memberId, wishListDTO.getProductId()); + @DeleteMapping + public void deleteWishList(@RequestBody WishListDTO wishListDTO) { + wishListService.deleteProduct(wishListDTO.getMemberId(), wishListDTO.getProductId()); } //상품 수정 - @PutMapping("/{memberId}") - public void updateWishList(@PathVariable int memberId, @RequestBody WishListDTO wishListDTO) { - wishListService.updateProduct(memberId, wishListDTO.getProductId(), + @PutMapping + public void updateWishList(@RequestBody WishListDTO wishListDTO) { + wishListService.updateProduct(wishListDTO.getMemberId(), wishListDTO.getProductId(), wishListDTO.getProductValue()); } diff --git a/src/main/java/gift/database/JdbcWishListRepository.java b/src/main/java/gift/database/JdbcWishListRepository.java index 491e45ffe..eff0d0530 100644 --- a/src/main/java/gift/database/JdbcWishListRepository.java +++ b/src/main/java/gift/database/JdbcWishListRepository.java @@ -53,7 +53,7 @@ public void setValues(PreparedStatement ps, int i) throws SQLException { @Override public int getBatchSize() { - return 100; + return 1; } }); } diff --git a/src/main/java/gift/dto/MemberDTO.java b/src/main/java/gift/dto/MemberDTO.java index 8d6673d10..fea9789e5 100644 --- a/src/main/java/gift/dto/MemberDTO.java +++ b/src/main/java/gift/dto/MemberDTO.java @@ -12,6 +12,9 @@ public class MemberDTO { private String password; private MemberRole role; + public MemberDTO() { + } + public MemberDTO(String email, String password, MemberRole role) { this.email = email; this.password = password; @@ -21,6 +24,13 @@ public MemberDTO(String email, String password, MemberRole role) { } } + public MemberDTO(Long id, String email, String password, MemberRole role) { + this.id = id; + this.email = email; + this.password = password; + this.role = role; + } + public Long getId() { return id; } diff --git a/src/main/java/gift/dto/WishListDTO.java b/src/main/java/gift/dto/WishListDTO.java index 32516d004..64bb8ae60 100644 --- a/src/main/java/gift/dto/WishListDTO.java +++ b/src/main/java/gift/dto/WishListDTO.java @@ -6,6 +6,15 @@ public class WishListDTO { private Long productId; private Integer productValue; + public WishListDTO() { + } + + public WishListDTO(Long memberId, Long productId, Integer productValue) { + this.memberId = memberId; + this.productId = productId; + this.productValue = productValue; + } + public Long getMemberId() { return memberId; } diff --git a/src/main/java/gift/service/AuthenticationTool.java b/src/main/java/gift/service/AuthenticationTool.java index 104e6e097..52c2fa014 100644 --- a/src/main/java/gift/service/AuthenticationTool.java +++ b/src/main/java/gift/service/AuthenticationTool.java @@ -19,13 +19,13 @@ public AuthenticationTool() { public String makeToken(Member member) { return Jwts.builder().claim("id", member.getId()) - .signWith(key).toString(); + .signWith(key).compact(); } public long parseToken(String token) { try { var claims = Jwts.parser().verifyWith(key).build().parseSignedClaims(token) - .getPayload(); + .getPayload();//TODO : 수정필요 return Long.parseLong(claims.get("id").toString()); } catch (JwtException e) { throw new MemberServiceException("JWT 인증 실패", HttpStatus.FORBIDDEN); diff --git a/src/main/java/gift/service/WishListService.java b/src/main/java/gift/service/WishListService.java index 8a9b6c9e9..0269423af 100644 --- a/src/main/java/gift/service/WishListService.java +++ b/src/main/java/gift/service/WishListService.java @@ -1,6 +1,7 @@ package gift.service; import gift.dto.WishListDTO; +import java.util.List; public interface WishListService { @@ -10,6 +11,6 @@ public interface WishListService { void updateProduct(long memberId, long productId, int productValue); - WishListDTO getWishList(long memberId); + List getWishList(long memberId); } diff --git a/src/main/java/gift/service/WishListServiceImpl.java b/src/main/java/gift/service/WishListServiceImpl.java index 205e1db26..a9208c8c2 100644 --- a/src/main/java/gift/service/WishListServiceImpl.java +++ b/src/main/java/gift/service/WishListServiceImpl.java @@ -4,6 +4,7 @@ import gift.dto.WishListDTO; import gift.model.WishList; import java.util.HashMap; +import java.util.List; import org.springframework.stereotype.Service; @Service @@ -35,7 +36,11 @@ public void updateProduct(long memberId, long productId, int productValue) { } @Override - public WishListDTO getWishList(long memberId) { - return null; + public List getWishList(long memberId) { + WishList wishList = jdbcWishListRepository.findByMemeberId(memberId); + var products = wishList.getWishList(); + return products.keySet().stream() + .map(key -> new WishListDTO(wishList.getMemberId(), key, products.get(key))).toList(); + } } diff --git a/src/test/java/gift/controller/MemberControllerTest.java b/src/test/java/gift/controller/MemberControllerTest.java index 21fdc4e50..1ec605bd6 100644 --- a/src/test/java/gift/controller/MemberControllerTest.java +++ b/src/test/java/gift/controller/MemberControllerTest.java @@ -1,7 +1,7 @@ package gift.controller; +import gift.dto.LoginMemberToken; import gift.dto.MemberDTO; -import gift.service.AuthenticationTool; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -102,7 +102,7 @@ void login() { //then responseSpec.expectStatus().isOk(); - responseSpec.expectBody(AuthenticationTool.class); + responseSpec.expectBody(LoginMemberToken.class); } @Test diff --git a/src/test/java/gift/controller/WebTestClientHelper.java b/src/test/java/gift/controller/WebTestClientHelper.java index 80d2128bd..57bf87679 100644 --- a/src/test/java/gift/controller/WebTestClientHelper.java +++ b/src/test/java/gift/controller/WebTestClientHelper.java @@ -71,6 +71,10 @@ public ResponseSpec delete(String url) { return webTestClient.delete().uri(url).accept(MediaType.APPLICATION_JSON).exchange(); } + public WebTestClient moreAction() { + return webTestClient; + } + /** * url을 만들어 준다. * From 7bb04e22ba5cae287bc477f45d2f135173ec898c Mon Sep 17 00:00:00 2001 From: mac <20j.code@gmail.com> Date: Mon, 8 Jul 2024 23:39:42 +0900 Subject: [PATCH 53/53] =?UTF-8?q?test=20:=20wishlist=20add=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/WishListControllerTest.java | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/test/java/gift/controller/WishListControllerTest.java b/src/test/java/gift/controller/WishListControllerTest.java index 2125c6c39..95ce02c28 100644 --- a/src/test/java/gift/controller/WishListControllerTest.java +++ b/src/test/java/gift/controller/WishListControllerTest.java @@ -1,11 +1,17 @@ package gift.controller; -import gift.service.AuthenticationTool; +import gift.dto.LoginMemberToken; +import gift.dto.MemberDTO; +import gift.dto.WishListDTO; +import java.util.HashMap; +import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.BodyInserters; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class WishListControllerTest { @@ -20,15 +26,17 @@ void setUp() { } @Test - @DisplayName("WebTestClientHelper 정상 작동 확인") - void isOk() { - - var response = webClient.get("/admin/products"); - - response.expectStatus().isOk(); - response.expectHeader().contentType("application/json"); - //response.expectBody().json(...) - + @DisplayName("위시 리스트 아이템 추가") + void addWishList() { + //given + String email = "abec"; + String password = "abecdddd"; + LoginMemberToken loginMemberToken = registerAndLogin(email, password); + webClient.moreAction().post().uri("api/wishlist") + .header("Authorization", loginMemberToken.getToken()) + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(new WishListDTO(0L, 123L, 1))) + .exchange().expectStatus().isOk(); } @@ -50,20 +58,20 @@ void deleteWishList() { } - private AuthenticationTool registerAndLogin(String email, String password) { - return null; - } - /* - private void addWishListPutRequest(long memberId, WishListDTO wishListDTO) { - ResponseSpec responseSpec = webClient.put().uri(uriBuilder -> { - return uriBuilder - .path("/api/member") - .queryParam("memberId", loginToken.getMemberId()) - .path("/wishlist") - .build(); - }).accept(MediaType.APPLICATION_JSON) - .body(BodyInserters.fromValue(wishListDTO)).exchange(); + private LoginMemberToken registerAndLogin(String email, String password) { + //register + MemberDTO memberDTO = new MemberDTO(email, password, null); + webClient.put("/api/member", memberDTO); + + //login + HashMap userInfo = new HashMap<>(); + userInfo.put("email", memberDTO.getEmail()); + userInfo.put("password", memberDTO.getPassword()); + String uri = webClient.uriMakeUseParameters("/api/member/login", userInfo); + String token = Objects.requireNonNull( + webClient.moreAction().get().uri(uri).accept(MediaType.APPLICATION_JSON).exchange() + .expectBody(LoginMemberToken.class).returnResult().getResponseBody()).getToken(); + return new LoginMemberToken(token); } - */ } \ No newline at end of file