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_이세진 3주차 과제 (1단계) #332

Open
wants to merge 5 commits into
base: ez23re
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 32 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,11 @@
# spring-gift-wishlist
# spring-gift-jpa

## 3단계 - 위시 리스트 기능 요구사항
이전 단계에서 로그인 후 받은 토큰을 사용하여 사용자별 위시 리스트 기능을 구현한다.
- 위시 리스트에 등록된 상품 목록을 조회할 수 있다.
- 위시 리스트에 상품을 추가, 삭제할 수 있다.
## 엔티피 연관관계 매핑 요구사항 : 작성해야할 가능에 대해
- 객체와 테이블 매핑 ->> @Entity @OneToMany 등을 이용
- @DataJpaTest를 사용하여 테스트 코드 작성 : 위시리스트 레포지토리 계속되는 테스트 실패 오류

### HandlerMethodArgumentResolver
컨트롤러 메서드에 진입하기 전처리를 통해 객체를 주입할 수 있다.

### JWT 생성 방법
SignatureAlgorithm 클래스 사용


## 2단계 - 회원 로그인 기능 요구사항
사용자가 회원 가입, 로그인, 추후 회원별 기능을 이용할 수 있도록 구현한다.
- 회원은 이메일과 비밀번호를 입력하여 가입한다.
- 토큰을 받으려면 이메일과 비밀번호를 보내야 하며, 가입한 이메일과 비밀번호가 일치하면 토큰이 발급된다.
- 토큰을 생성하는 방법에는 여러 가지가 있다. 방법 중 하나를 선택한다.
- (시간되면) joinForm.html 을 만들어 컨트롤러와 타임리프를 사용하여 연결한다.


## 1단계 - 유효성 검사 및 예외 처리 기능 요구사항
상품을 추가하거나 수정하는 경우, 클라이언트로부터 잘못된 값이 전달될 수 있다.
잘못된 값이 전달되면 클라이언트가 어떤 부분이 왜 잘못되었는지 인지할 수 있도록 응답을 제공한다.
- 상품 이름은 공백을 포함하여 최대 15자까지 입력할 수 있다.
- 특수 문자
가능: ( ), [ ], +, -, &, /, _
그 외 특수 문자 사용 불가
- "카카오"가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있다. -> 일단 메시지만 출력하도록 구현한다.

### validation
spring-boot-starter-validation 의존성을 명시적으로 추가한다.
`implementation 'spring-boot-starter-validation'`




## 3주차부터 -> 아래와 같이 기능별 보다 객체별로 구조를 나누면 어떤가요? 아니면 또 한번, 기능별로 구분할까요?
## 현재 코드 구조
```plaintext
└── src
└── main
Expand All @@ -54,21 +23,27 @@ spring-boot-starter-validation 의존성을 명시적으로 추가한다.
│ │ └── GlobalExceptionHandler.java
│ │
│ ├── member
│ │ ├── Member.java
│ │ ├── MemberController.java
│ │ ├── MemberDto.java
│ │ ├── MemberRepository.java
│ │ ├── MemberService.java
│ │ └── TokenService.java
│ │ ├── controller
│ │ │ └── MemberController.java
│ │ ├── dto
│ │ │ └── MemberDto.java
│ │ ├── model
│ │ │ └── Member.java
│ │ ├── repostitory
│ │ │ └── MemberRepository.java
│ │ └── service
│ │ ├── MemberService.java
│ │ └── TokenService.java
│ │
│ ├── product
│ │ ├── product.java
│ │ ├── ProductController.java
│ │ ├── ProductDao.java
│ │ ├── ProductDto.java
│ │ ├── ProductModel.java
│ │ ├── ProductName.java
│ │ └── ProductService.java
│ │ ├── controller
│ │ │ └── ProductController
│ │ ├── model
│ │ │ └── Product.java
│ │ ├── repository
│ │ │ └── ProductRepository.java
│ │ └── service
│ │ └── ProductService.java
│ │
│ └── wishlist
│ ├── WishList.java
Expand All @@ -85,4 +60,11 @@ spring-boot-starter-validation 의존성을 명시적으로 추가한다.
├── add.html
├── edit.html
├── list.html
└── view.html
└── view.html


└── src
└── main
├── java
└── gift
└── MemberRepositoryTest.java
13 changes: 3 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,17 @@ repositories {
* **/
dependencies {
implementation 'jakarta.persistence:jakarta.persistence-api:3.0.0'
// Spring Boot Starter JPA
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// H2 Database
runtimeOnly 'com.h2database:h2'
// Spring Boot Starter Web
implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Boot Starter Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// Password Encoder
implementation 'org.springframework.security:spring-security-crypto'
compileOnly 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.2'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
Expand Down
67 changes: 7 additions & 60 deletions src/main/java/gift/admin/AdminController.java
Original file line number Diff line number Diff line change
@@ -1,68 +1,15 @@
package gift.admin;

import gift.product.ProductDto;
import gift.product.ProductService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
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.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Controller
@RestController
@RequestMapping("/admin")
public class AdminController {
private final ProductService productService;

@Autowired
public AdminController(ProductService productService) {
this.productService = productService;
}

@GetMapping("/products/list")
public String listProducts(Model model) {
List<ProductDto> products = productService.findAll();
model.addAttribute("products", products);
return "list"; // list.html 파일 보여주기
}

@GetMapping("/products/view/{id}")
public String viewProduct(@PathVariable Long id, Model model) {
ProductDto product = productService.findById(id);
model.addAttribute("product", product);
return "view"; // view.html 파일 보여주기
}

@GetMapping("/products/add")
public String showAddProductForm(Model model) {
model.addAttribute("productDto", new ProductDto());
return "add"; // add.html 파일 보여주기
}

@PostMapping("/products/add")
public String addProduct(@Valid @ModelAttribute("productDto") ProductDto productDto, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add"; // 에러가 있으면 다시 add.html 보여주기
}
productService.save(productDto);
return "redirect:/admin/products/list";
}

@GetMapping("/products/edit/{id}")
public String showEditProductForm(@PathVariable Long id, Model model) {
ProductDto product = productService.findById(id);
model.addAttribute("productDto", new ProductDto(product.id(), product.name(), product.price(), product.imgUrl()));
return "edit";
}

@PostMapping("/products/edit/{id}")
public String editProduct(@PathVariable Long id, @Valid @ModelAttribute("productDto") ProductDto productDto, BindingResult result) {
if (result.hasErrors()) {
return "edit"; // 에러가 있으면 다시 edit.html 보여주기
}
productService.update(id, productDto);
return "redirect:/admin/products/list";
@GetMapping
public String getAdmin() {
return "관리자 화면";
}
}
4 changes: 0 additions & 4 deletions src/main/java/gift/exception/ForbiddenException.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package gift.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.FORBIDDEN)
public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
Expand Down
38 changes: 8 additions & 30 deletions src/main/java/gift/exception/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,24 @@

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 java.util.HashMap;
import java.util.Map;

@ControllerAdvice
public class GlobalExceptionHandler {

// 특정 예외가 발생 했을 때 () 안에 메소드가 호출되도록 하는 어노테이션
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();

// 유효성 검사 실패 필드랑 메시지 추출
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);

// 클라이언트에 응답 반환
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}

// 상품명에 '카카오' 사용한 경우에 호출되도록 하는 어노테이션
@ExceptionHandler(KakaoProductException.class)
public ResponseEntity<String> handleKakaoProductException(KakaoProductException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<String> handleForbiddenException(ForbiddenException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);
}

// 예외 메시지와 함께 401 (클라이언트가 인증되지 않음. 유효한 인증 자격 증명이 필요)를 반환하는 메소드
@ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<String> handleUnauthorizedException(UnauthorizedException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.UNAUTHORIZED);
public ResponseEntity<String> handleUnauthorizedException(UnauthorizedException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.UNAUTHORIZED);
}

// 예외 메시지와 함께 403 (클라이언트가 인증되었으나, 요청한 리소스에 접근할 권한이 없음)를 반환하는 메소드
@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<String> handleForbiddenException(ForbiddenException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN);
@ExceptionHandler(KakaoProductException.class)
public ResponseEntity<String> handleKakaoProductException(KakaoProductException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}

}
3 changes: 3 additions & 0 deletions src/main/java/gift/exception/UnauthorizedException.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}

public UnauthorizedException(String invalidTokenFormat, Exception e) {
}
}
42 changes: 0 additions & 42 deletions src/main/java/gift/member/Member.java

This file was deleted.

47 changes: 0 additions & 47 deletions src/main/java/gift/member/MemberController.java

This file was deleted.

Loading