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_정진택 2주차 과제 (3단계) #394

Open
wants to merge 54 commits into
base: jjt4515
Choose a base branch
from
Open
Changes from 40 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
93ec964
spring-gift-product -> spring-gift-wishlist
jjt4515 Jul 3, 2024
1e6e759
Update README.md
jjt4515 Jul 3, 2024
deb4438
fix: ProductDBRepository의 save 로직 수정
jjt4515 Jul 3, 2024
38b1f06
chore: validation 의존성 추가
jjt4515 Jul 4, 2024
de9e5fc
feat: ProductRequest validation 추가
jjt4515 Jul 4, 2024
6679f10
feat: ProductService 예외 처리 로직 추가
jjt4515 Jul 4, 2024
803f1fa
feat: ProductController 로직 수정
jjt4515 Jul 4, 2024
8bcde30
style: ProductDBRepository 로직 수정
jjt4515 Jul 4, 2024
12cb6f4
fix: test 코드 수정
jjt4515 Jul 4, 2024
ed41371
feat: GlobalExceptionHandler 추가
jjt4515 Jul 4, 2024
1716379
feat: InvalidProductDataException, ProductNotFoundException 추가
jjt4515 Jul 4, 2024
39e7ddd
feat: validation 수정
jjt4515 Jul 4, 2024
680146d
Update README.md
jjt4515 Jul 4, 2024
178cbc0
conflict
jjt4515 Jul 4, 2024
b07581e
Update README.md
jjt4515 Jul 4, 2024
26d234a
confilct resolve
jjt4515 Jul 4, 2024
ba19b03
Update README.md
jjt4515 Jul 4, 2024
ac6aeed
Update README.md
jjt4515 Jul 5, 2024
027c703
Update README.md
jjt4515 Jul 5, 2024
2a06e9d
feat: Member클래스 추가
jjt4515 Jul 5, 2024
8119157
feat: MemeberJDBCRepository 추가
jjt4515 Jul 5, 2024
58ab529
feat: MemberService 추가
jjt4515 Jul 5, 2024
4d600ca
feat: CustomUserDetailsService 추가
jjt4515 Jul 5, 2024
9f80601
feat: MemberController 추가
jjt4515 Jul 5, 2024
dcaa848
feat: Member Service 예외 처리
jjt4515 Jul 5, 2024
c837e34
feat: MemberController, MemberService 로직 수정
jjt4515 Jul 5, 2024
3d8bab4
Update README.md
jjt4515 Jul 5, 2024
48acd30
feat: AccessDeniedException 추가
jjt4515 Jul 5, 2024
f5b6b2a
Merge branch 'step2' of https://github.com/jjt4515/spring-gift-wishli…
jjt4515 Jul 5, 2024
5a001e9
Update README.md
jjt4515 Jul 5, 2024
26fd667
feat: WishlistItem추가
jjt4515 Jul 5, 2024
b6c5192
feat: WishlistJDBCRepository 추가
jjt4515 Jul 5, 2024
aa49dda
feat: WishlistService 추가
jjt4515 Jul 5, 2024
5d302bd
feat: WishlistController 추가
jjt4515 Jul 5, 2024
54fbd1a
feat: LoginMember 추가
jjt4515 Jul 5, 2024
3c00aa0
feat: WishlistJDBCRepository 로직 수정
jjt4515 Jul 5, 2024
2a2d357
Merge branch 'step3' of https://github.com/jjt4515/spring-gift-wishli…
jjt4515 Jul 5, 2024
2dd7945
Update README.md
jjt4515 Jul 5, 2024
4899053
feat: schema.sql 수정
jjt4515 Jul 5, 2024
34489c8
Merge branch 'step3' of https://github.com/jjt4515/spring-gift-wishli…
jjt4515 Jul 5, 2024
153d72b
style: ProductService 수정
jjt4515 Jul 7, 2024
49b4ec8
refactor: 모든 Request 수정
jjt4515 Jul 8, 2024
c94767f
refactor: Exception Handler 수정
jjt4515 Jul 8, 2024
962ce6d
chore: dto 패키지 추가
jjt4515 Jul 8, 2024
61825ac
refactor: kakao포함 문구 에러 처리
jjt4515 Jul 8, 2024
5329fb1
docs: schema.sql 수정
jjt4515 Jul 8, 2024
87f5010
refactor: secretKey 변경
jjt4515 Jul 8, 2024
748dfb9
refactor: generateJwtToken 수정
jjt4515 Jul 8, 2024
73410f6
refactor: 토큰 인증 수정
jjt4515 Jul 8, 2024
889881d
refactor: 이메일 중복 에러 처리
jjt4515 Jul 8, 2024
8eaf29a
refactor: 위시리스트 컨트롤러, 서비스 수정
jjt4515 Jul 8, 2024
147d1ad
refactor: 토큰인증 수정
jjt4515 Jul 8, 2024
7228fad
refactor: ProductService 예외 처리 추가
jjt4515 Jul 8, 2024
5a4f6a4
fix: 충돌 해결
jjt4515 Jul 8, 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
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,57 @@
# spring-gift-wishlist

## step1

### 기능 요구 사항
상품을 추가하거나 수정하는 경우, 클라이언트로부터 잘못된 값이 전달될 수 있다. 잘못된 값이 전달되면 클라이언트가 어떤 부분이 왜 잘못되었는지 인지할 수 있도록 응답을 제공한다.

- 상품 이름은 공백을 포함하여 최대 15자까지 입력할 수 있다.
- 특수 문자
- 가능: ( ), [ ], +, -, &, /, _
- 그 외 특수 문자 사용 불가
- "카카오"가 포함된 문구는 담당 MD와 협의한 경우에만 사용할 수 있다.

### 구현 기능 목록

- validation
- 상품이름 글자수 최대 15자
- 상품이름 특수 문자 일부만 사용가능( ), [ ], +, -, &, /, _
- 상품이름에 "카카오"가 포함된 문구 제한
- 가격은 양의 정수
- 예외처리
- 존재하지 않는 상품인 경우
- 상품 데이터가 유효하지 않는 경우

## step2

### 기능 요구 사항
상품 관리 코드를 옮겨 온다. 코드를 옮기는 방법에는 디렉터리의 모든 파일을 직접 복사하여 붙여 넣는 것부터 필요한 일부 파일만 이동하는 것, Git을 사용하는 것까지 여러 가지 방법이 있다. 코드 이동 시 반드시 리소스 파일, 프로퍼티 파일, 테스트 코드 등을 함께 이동한다.
사용자가 회원 가입, 로그인, 추후 회원별 기능을 이용할 수 있도록 구현한다.

- 회원은 이메일과 비밀번호를 입력하여 가입한다.
- 토큰을 받으려면 이메일과 비밀번호를 보내야 하며, 가입한 이메일과 비밀번호가 일치하면 토큰이 발급된다.
- 토큰을 생성하는 방법에는 여러 가지가 있다. 방법 중 하나를 선택한다.
- (선택) 회원을 조회, 추가, 수정, 삭제할 수 있는 관리자 화면을 구현한다.

### 구현 기능 목록
- 멤버 회원가입
- 토큰 반환
- 예외 처리
- 멤버 로그인
- 토큰 반환
- 예외 처리

## step3

### 기능 요구 사항
이전 단계에서 로그인 후 받은 토큰을 사용하여 사용자별 위시 리스트 기능을 구현한다.

- 위시 리스트에 등록된 상품 목록을 조회할 수 있다.
- 위시 리스트에 상품을 추가할 수 있다.
- 위시 리스트에 담긴 상품을 삭제할 수 있다.

### 구현 기능 목록
- 멤버별 위시 리스트
- 위시 리스트 상품 목록 조회
- 위시 리스트 상품 추가
- 위시 리스트 상품 삭제
- 예외 처리
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -21,9 +21,15 @@ 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'
implementation 'org.springframework.boot:spring-boot-starter-security'
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') {
48 changes: 48 additions & 0 deletions src/main/java/gift/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package gift.controller;

import gift.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
public class MemberController {

private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}

@PostMapping("/members/register")
public ResponseEntity<TokenResponse> register(@RequestBody MemberRequest memberRequest) {
String token = memberService.register(memberRequest);
TokenResponse response = new TokenResponse(token);
return ResponseEntity.ok(response);
}

@PostMapping("/members/login")
public ResponseEntity<TokenResponse> login(@RequestBody MemberRequest memberRequest) {
String token = memberService.authenticate(memberRequest);
TokenResponse response = new TokenResponse(token);
return ResponseEntity.ok(response);
}

private static class TokenResponse {
private String token;

public TokenResponse(String token) {
this.token = token;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/gift/controller/MemberRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gift.controller;

import jakarta.validation.constraints.*;

public class MemberRequest {

@NotBlank(message = "이메일은 필수 항목입니다.")
@Email(message = "유효한 이메일 주소를 입력하세요.")
private String email;

@NotBlank(message = "비밀번호는 필수 항목입니다.")
private String password;

public MemberRequest() {}

public MemberRequest(String email, String password) {
this.email = email;
this.password = password;
}

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;
}
}
14 changes: 6 additions & 8 deletions src/main/java/gift/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

import gift.domain.Product;
import gift.service.ProductService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@@ -40,22 +41,19 @@ public String newProductForm(Model model){
}

@PostMapping("/api/products")
public String addProduct(@ModelAttribute ProductRequest productRequest) {
public String addProduct(@Valid @ModelAttribute ProductRequest productRequest) {
productService.register(productRequest);
return "redirect:/api/products";
}

@GetMapping("/api/products/edit/{id}")
public String editProductForm(@PathVariable long id, Model model){
Optional<Product> product = productService.findOne(id);
if (product.isPresent()){
model.addAttribute("product", product.get());
return "product-edit-form";
};
return "redirect:/api/products";
Product product = productService.findOne(id);
model.addAttribute("product", product);
return "product-edit-form";
}
@PostMapping("/api/products/edit/{id}")
public String updateProduct(@PathVariable Long id, @ModelAttribute ProductRequest productRequest) {
public String updateProduct(@PathVariable Long id, @Valid @ModelAttribute ProductRequest productRequest) {
productService.update(id, productRequest);
return "redirect:/api/products";
}
28 changes: 26 additions & 2 deletions src/main/java/gift/controller/ProductRequest.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
package gift.controller;

import gift.domain.Product;
import gift.exception.InvalidProductDataException;
import jakarta.validation.constraints.*;


public class ProductRequest {

@NotBlank(message = "상품 이름은 필수 항목입니다.")
@Size(max = 15, message = "상품 이름은 최대 15자까지 입력할 수 있습니다.")
@Pattern(
regexp = "^[a-zA-Z0-9가-힣ㄱ-ㅎㅏ-ㅣ()\\[\\]+\\-&/_ ]*$",
message = "상품 이름에 허용되지 않는 특수 문자가 포함되어 있습니다."
)
private String name;

@NotNull(message = "가격을 입력하세요")
@Positive(message = "가격은 양의 정수여야 합니다.")
private long price;

private String imageUrl;

public ProductRequest(String name, long price, String imageUrl) {
if (name.contains("카카오") && !isApprovedByMD()) {
throw new InvalidProductDataException("상품 이름에 '카카오'를 포함할 수 없습니다. 담당 MD와 협의하세요.");
}
this.name = name;
this.price = price;
this.imageUrl = imageUrl;
}

public ProductRequest() {
}
public ProductRequest() {}

public String getName(){
return name;
}
public void setName(String name){
if (name.contains("카카오") && !isApprovedByMD()) {
throw new InvalidProductDataException("상품 이름에 '카카오'를 포함할 수 없습니다. 담당 MD와 협의하세요.");
}
this.name = name;
}

private boolean isApprovedByMD() {
return false;
}

public long getPrice(){
return price;
}
36 changes: 36 additions & 0 deletions src/main/java/gift/controller/WishlistController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package gift.controller;

import gift.domain.WishlistItem;
import gift.service.WishlistService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;


@RestController
public class WishlistController {

private final WishlistService wishlistService;

@Autowired
public WishlistController(WishlistService wishlistService) {
this.wishlistService = wishlistService;
}

@PostMapping("/wishlist/add")
public void addToWishlist(@Valid @RequestBody WishlistNameRequest request) {
wishlistService.addItemToWishlist(request);
}

@DeleteMapping("/wishlist/delete")
public void deleteFromWishlist(@Valid @RequestBody WishlistIdRequest request) {
wishlistService.deleteItemFromWishlist(request);
}

@GetMapping("/wishlist/get/{memberId}")
public List<WishlistItem> getWishlist(@PathVariable Long memberId) {
return wishlistService.getWishlistByMemberId(memberId);
}
Copy link

Choose a reason for hiding this comment

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

RESTful API 에 맞춰서 api path 를 변경해볼 수 있을 것 같아요😀

첫 번째, URI는 정보의 자원을 표현해야 한다.
두 번째, 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.

fyi; RESTful API 문서

Copy link
Author

Choose a reason for hiding this comment

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

넵 변경했습니다!

Copy link

Choose a reason for hiding this comment

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

좋습니다👍

    @DeleteMapping("/{itemId}")
    public void deleteFromWishlist(@PathVariable Long itemId, @RequestParam Long memberId) {
        wishlistService.deleteItemFromWishlist(itemId, memberId);
    }

    @GetMapping("/{memberId}")
    public List<WishlistItem> getWishlist(@PathVariable Long memberId) {
        return wishlistService.getWishlistByMemberId(memberId);
    }

api 를 호출한다고 생각했을 때, DELETE wishlist/1 은 itemId 라고 생각할 수 도 있을 것 같아요! memberId 이기때문에 member 라는 리소스를 path 에 추가해줘도 좋을 것 같아요.

    @GetMapping("/member/{memberId}")
    public List<WishlistItem> getWishlist(@PathVariable Long memberId) {
        return wishlistService.getWishlistByMemberId(memberId);
    }

}
32 changes: 32 additions & 0 deletions src/main/java/gift/controller/WishlistIdRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gift.controller;
import jakarta.validation.constraints.*;

public class WishlistIdRequest {

@NotNull(message = "Item ID를 입력하세요")
private Long itemId;

@NotNull(message = "Member ID를 입력하세요")
private Long memberId;

public WishlistIdRequest(Long itemId, Long memberId) {
this.itemId = itemId;
this.memberId = memberId;
}

public Long getItemId() {
return itemId;
}

public void setItemId(Long itemId) {
this.itemId = itemId;
}

public Long getMemberId() {
return memberId;
}

public void setMemberId(Long memberId) {
this.memberId = memberId;
}
}
32 changes: 32 additions & 0 deletions src/main/java/gift/controller/WishlistNameRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package gift.controller;

import jakarta.validation.constraints.NotNull;

public class WishlistNameRequest {

@NotNull(message = "Member ID를 입력하세요")
private Long memberId;
@NotNull(message = "Item Name을 입력하세요")
private String itemName;

public WishlistNameRequest(Long memberId, String itemName) {
this.memberId = memberId;
this.itemName = itemName;
}

public Long getMemberId() {
return memberId;
}

public void setMemberId(Long memberId) {
this.memberId = memberId;
}

public String getItemName() {
return itemName;
}

public void setItemName(String itemName) {
this.itemName = itemName;
}
}
Loading