Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

강원대 BE_박지희 4주차 과제 (1단계) #228

Open
wants to merge 20 commits into
base: peacefullyquietly
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9d2da5f
init : step0 - 3주차 jpa 코드 리뷰 반영
peacefullyquietly Jul 16, 2024
380f7ad
docs : README.md 수정
peacefullyquietly Jul 18, 2024
5cd354d
feat : CategoryDTO 클래스 작성
peacefullyquietly Jul 18, 2024
25a214e
feat : CategoryController.class 작성
peacefullyquietly Jul 18, 2024
22a925d
feat : CategoryEntity 클래스 작성
peacefullyquietly Jul 18, 2024
cf40b40
feat : CategoryService 클래스 작성
peacefullyquietly Jul 18, 2024
9f4dea6
feat : CategoryRepository 클래스 작성
peacefullyquietly Jul 18, 2024
1041e7a
refactor : 단일 책임 원칙에 따른 메서드 분리
peacefullyquietly Jul 18, 2024
c94e4cb
refactor : 단일 책임 원칙에 따른 메서드 분리
peacefullyquietly Jul 18, 2024
840491d
refactor : 컨트롤러 레이어 개선
peacefullyquietly Jul 18, 2024
e259339
Merge branch 'tmp_step1' into step1
peacefullyquietly Jul 18, 2024
de23358
refactor : 테스트코드 내 @Transaction 추가 및 오류 해결
peacefullyquietly Jul 18, 2024
5ca217c
test : WishListRepositoryTest 클래스 작성
peacefullyquietly Jul 18, 2024
a7157eb
refactor : 요구사항에 맞게 유효성 검사 부분 수정
peacefullyquietly Jul 18, 2024
dfeb282
refactor : validation 추가
peacefullyquietly Jul 19, 2024
5e4a0a7
refactor : 예외처리 방법 수정
peacefullyquietly Jul 19, 2024
67dfd4a
refactor : 잘못된 서비스 로직 수정
peacefullyquietly Jul 19, 2024
d9de226
refactor : 서비스 로직에따른 변경 사항
peacefullyquietly Jul 19, 2024
d7ff863
refactor : 예외처리 방법 변경에 따른 수정사항
peacefullyquietly Jul 19, 2024
0e1c777
refactor : 에러 메시지 수정
peacefullyquietly Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
# spring-gift-enhancement
# spring-gift-enhancement

## Step1
- 기능 요구사항
- 상품 정보에 카테고리를 추가한다. 상품과 카테고리 모델 간의 관계를 고려하여 설계하고 구현한다.
- 상품에는 항상 하나의 카테고리가 있어야 한다.
- 상품 카테고리는 수정할 수 있다.
- 관리자 화면에서 상품을 추가할 때 카테고리를 지정할 수 있다.
- 카테고리는 1차 카테고리만 있으며 2차 카테고리는 고려하지 않는다.
- 카테고리의 예시는 아래와 같다.
- 교환권, 상품권, 뷰티, 패션, 식품, 리빙/도서, 레저/스포츠, 아티스트/캐릭터, 유아동/반려, 디지털/가전, 카카오프렌즈, 트렌드 선물, 백화점, ...
- 아래 예시와 같이 HTTP 메시지를 주고받도록 구현한다.

- Request
<pre>
GET /api/categories HTTP/1.1
</pre>

- Response
<pre>
<code>
HTTP/1.1 200
Content-Type: application/json
[
{
"id": 91,
"name": "교환권",
"color": "#6c95d1",
"imageUrl": "https://gift-s.kakaocdn.net/dn/gift/images/m640/dimm_theme.png",
"description": ""
}
]
</code>
</pre>
3 changes: 1 addition & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ repositories {

dependencies {
// MySQL
runtimeOnly 'com.mysql:mysql-connector-j' // MySQL 드라이버 의존성 추가
testRuntimeOnly 'mysql:mysql-connector-java'
runtimeOnly 'mysql:mysql-connector-java:8.0.30' // MySQL 드라이버 의존성 추가

// JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand Down
44 changes: 12 additions & 32 deletions src/main/java/gift/controller/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@

import gift.domain.ProductDTO;
import gift.service.ProductService;
import gift.service.ProductServiceStatus;
import jakarta.validation.Valid;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/admin/products")
Expand All @@ -36,25 +32,19 @@ public String addProductForm(Model model) {
}

@PostMapping("/add")
public ModelAndView addProduct(@ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) {
public String addProduct(@ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return new ModelAndView("create");
return "create";
}

ProductServiceStatus response = productService.createProduct(productDTO);
if (response == ProductServiceStatus.SUCCESS) {
return new ModelAndView("redirect:/admin/products");
}

ModelAndView mav = new ModelAndView("create");
mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
mav.addObject("error", "상품 추가 실패");
return mav;
productService.createProduct(productDTO);
return "redirect:/admin/products";
}

@GetMapping("/update/{id}")
public String editProductForm(@PathVariable Long id, Model model) {
Optional<ProductDTO> productDTO = productService.getProduct(id);
var productDTO = productService.getProduct(id)
.orElse(null); // Optional에서 직접 null을 반환
if (productDTO == null) {
return "redirect:/admin/products";
}
Expand All @@ -63,28 +53,18 @@ public String editProductForm(@PathVariable Long id, Model model) {
}

@PostMapping("/update/{id}")
public ModelAndView editProduct(@PathVariable Long id, @ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) {
public String editProduct(@PathVariable Long id, @ModelAttribute @Valid ProductDTO productDTO, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return new ModelAndView("update");
return "update";
}

ProductServiceStatus response = productService.editProduct(id, productDTO);
if (response == ProductServiceStatus.SUCCESS) {
return new ModelAndView("redirect:/admin/products");
}

ModelAndView mav = new ModelAndView("update");
mav.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
mav.addObject("error", "상품 수정 실패");
return mav;
productService.editProduct(id, productDTO);
return "redirect:/admin/products";
}

@PostMapping("/delete/{id}")
public String deleteProduct(@PathVariable Long id) {
ProductServiceStatus response = productService.deleteProduct(id);
if (response == ProductServiceStatus.SUCCESS) {
return "redirect:/admin/products"; // 성공적으로 삭제 후 목록 페이지로 리다이렉트
}
return "error"; // 실패 시 에러 페이지로 리다이렉트
productService.deleteProduct(id);
return "redirect:/admin/products";
}
}
60 changes: 60 additions & 0 deletions src/main/java/gift/controller/CategoryController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package gift.controller;

import gift.domain.CategoryDTO;
import gift.service.CategoryService;
import jakarta.validation.Valid;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;

@RestController
@RequestMapping("/api/category")
public class CategoryController {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상품추가할때 바로 카테고리를 지정해야하는 요구사항이 있는것으로 알고있는데요, 상품쪽 controller에 수정이 필요할것같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 수정해보겠습니다!

private final CategoryService categoryService;

@Autowired
public CategoryController(CategoryService categoryService){
this.categoryService = categoryService;
}

@GetMapping
public ResponseEntity<List<CategoryDTO>> getCategory() {
List<CategoryDTO> response = categoryService.getCategories();
return new ResponseEntity<>(response, HttpStatus.OK);
}

@GetMapping("/{id}")
public ResponseEntity<CategoryDTO> getCategoryById(@PathVariable Long id) throws Exception {
CategoryDTO response = categoryService.getCategory(id);
return new ResponseEntity<>(response, HttpStatus.OK);
}

@PutMapping
public ResponseEntity<CategoryDTO> addCategory(@Valid @RequestBody CategoryDTO category) {
CategoryDTO createdCategory = categoryService.createCategory(category);
return new ResponseEntity<>(createdCategory, HttpStatus.CREATED);
}

@PostMapping("/{id}")
public ResponseEntity<CategoryDTO> updateCategory(@PathVariable Long id, @Valid @RequestBody CategoryDTO category) {
CategoryDTO updatedCategory = categoryService.updateCategory(id, category);
return new ResponseEntity<>(updatedCategory, HttpStatus.OK);
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteCategory(@PathVariable Long id) {
categoryService.deleteCategory(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}


}
25 changes: 5 additions & 20 deletions src/main/java/gift/controller/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import gift.domain.MemberDTO;
import gift.service.JwtUtil;
import gift.service.MemberService;
import gift.service.MemberServiceStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -28,29 +27,15 @@ public MemberController(MemberService memberService, JwtUtil jwtUtil) {

@PostMapping("/register")
public ResponseEntity<?> registerUser(@RequestBody MemberDTO memberDTO) {
MemberServiceStatus status = memberService.save(memberDTO);

if (status == MemberServiceStatus.EMAIL_ALREADY_EXISTS) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(Collections.singletonMap("error", "Email already exists"));
}

return ResponseEntity.ok().body(Collections.singletonMap("message", "Success"));
memberService.save(memberDTO);
return new ResponseEntity<>(Collections.singletonMap("status", "SUCCESS"), HttpStatus.OK);
}

@PostMapping("/login")
public ResponseEntity<?> loginUser(@RequestBody MemberDTO memberDTO) {
MemberEntity authenticatedMember = memberService.authenticateToken(memberDTO);

if (authenticatedMember == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Collections.singletonMap("error", "Invalid email or password"));
}

Long userId = authenticatedMember.getId();

// 토큰 생성 및 응답
String token = jwtUtil.generateToken(authenticatedMember.getEmail(), userId);
Map<String, String> responseBody = Collections.singletonMap("token", token);
return ResponseEntity.ok().body(responseBody);
String token = jwtUtil.generateToken(authenticatedMember.getEmail(), authenticatedMember.getId());
Map<String, String> response = Collections.singletonMap("token", token);
return new ResponseEntity<>(response, HttpStatus.OK);
}

}
17 changes: 9 additions & 8 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import gift.domain.ProductDTO;
import gift.service.ProductService;
import gift.service.ProductServiceStatus;
import jakarta.validation.constraints.Min;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -25,9 +24,9 @@ private ProductController(ProductService productService) {

// 상품 추가(Create)
@PostMapping
public ResponseEntity<ProductServiceStatus> addProduct(@RequestBody ProductDTO productDTO) {
ProductServiceStatus response = productService.createProduct(productDTO);
return new ResponseEntity<>(response, HttpStatus.CREATED);
public ResponseEntity<?> addProduct(@RequestBody ProductDTO productDTO) {
productService.createProduct(productDTO);
return new ResponseEntity<>(HttpStatus.CREATED);
}

// 단일 상품 조회(Read)
Expand All @@ -54,13 +53,15 @@ public ResponseEntity<Page<ProductDTO>> selectAllProducts(

// 상품 수정(Update)
@PutMapping("/{id}")
public ProductServiceStatus updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) {
return productService.editProduct(id, productDTO);
public ResponseEntity<?> updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) {
productService.editProduct(id, productDTO);
return ResponseEntity.ok("상품이 추기 되었습니다.");
}

// 상품 삭제(Delete)
@DeleteMapping("/{id}")
public ProductServiceStatus deleteProduct(@PathVariable Long id) {
return productService.deleteProduct(id);
public ResponseEntity<?> deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
return ResponseEntity.ok("상품이 삭제 되었습니다.");
}
}
49 changes: 49 additions & 0 deletions src/main/java/gift/domain/CategoryDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package gift.domain;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public class CategoryDTO {
private Long id;

@NotBlank(message = "카테고리 이름은 필수 입력 항목입니다.")
private String name;

@Pattern(regexp = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", message = "올바른 HEX 색상 코드로 입력해 주세요")
private String color;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저장될때는 nullable하지 않게 되어있는데 앞에서도 검증이 필요할것같습니다.


@Pattern(regexp = "^https?://.*$", message = "올바른 이미지 URL 형식으로 입력해 주세요")
private String imageUrl;

@Size(max = 255, message = "설명은 최대 255글자 이하로 입력해 주세요")
private String description;

public CategoryDTO(Long id, String name, String color, String imageUrl, String description) {
this.id = id;
this.name = name;
this.color = color;
this.imageUrl = imageUrl;
this.description = description;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public String getColor() {
return color;
}

public String getImageUrl() {
return imageUrl;
}

public String getDescription() {
return description;
}
}
12 changes: 10 additions & 2 deletions src/main/java/gift/domain/ProductDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ public class ProductDTO {
@Pattern(regexp = "^(?!.*카카오).*$", message = "'카카오'가 포함된 문구는 담당 MD와 협의 후 사용 바랍니다.")
private String name;


@PositiveOrZero(message = "가격은 0 이상의 숫자를 입력해 주세요.")
private int price;

@Pattern(regexp = "^https?://.*$", message = "올바른 이미지 URL 형식으로 입력해 주세요")
private String imageUrl;

@NotNull(message = "카테고리는 필수 항목입니다.")
private Long categoryId;

public ProductDTO(){}

/**
Expand All @@ -31,12 +33,14 @@ public ProductDTO(){}
* @param name 상품의 이름
* @param price 상품의 가격
* @param imageUrl 상품의 이미지 주소
* @param categoryId 카테고리 고유의 ID
*/
public ProductDTO(Long id, String name, int price, String imageUrl){
public ProductDTO(Long id, String name, int price, String imageUrl, Long categoryId){
this.id = id;
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
this.categoryId = categoryId;
}


Expand All @@ -57,6 +61,8 @@ public String getImageUrl() {
return imageUrl;
}

public Long getCategoryId() {return categoryId;}

// Setter 메서드들
public void setId(Long id) {
this.id = id;
Expand All @@ -73,4 +79,6 @@ public void setPrice(int price) {
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

public void setCategoryId(Long categoryId) {this.categoryId = categoryId;}
}
Loading