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

제버릇 개 못주고 깔끔하게 구현하지 못한 누누 #9

Open
wants to merge 74 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
1c77a83
[1단계 - 상품 관리 기능] 누누(송은우) 미션 제출합니다. (#192)
be-student May 2, 2023
19ffe1f
feat: 빈 입력값 검증 추가
be-student May 2, 2023
eb76bb9
feat: product 패키지로 product 관련 클래스 분리
be-student May 2, 2023
059164c
docs: readme.md 작성
be-student May 3, 2023
af03bf1
feat: 필터 작성
be-student May 3, 2023
c3ad550
feat: argument resolver 작성
be-student May 3, 2023
75260e2
feat: user 도메인 구현
be-student May 3, 2023
80e76c0
docs: 구조 변경
be-student May 3, 2023
b61407e
chore: 의미 없는 @WebMvcTest 제거
be-student May 3, 2023
bb5f60b
feat: userEntity 작성
be-student May 3, 2023
ae807db
refactor: product 관련 기능들은 product 패키지로 분리
be-student May 3, 2023
d5b2a08
feat: userRepository 작성
be-student May 3, 2023
9195b0e
feat: userRepository 작성
be-student May 3, 2023
01e4f25
fix: 깨진 테스트 수정
be-student May 3, 2023
65094cc
feat: sql 테이블 작성
be-student May 3, 2023
0b93eb1
feat: cart 도메인 작성
be-student May 3, 2023
27cd0a6
feat: cartRepository 인터페이스 작성
be-student May 3, 2023
4d2dcf3
refactor: restController 에서 apiController 로 네이밍 변경
be-student May 3, 2023
9e86819
feat: view controller 각각 페이지에 따라 분리
be-student May 3, 2023
592683f
chore: sql 필드 이름 변경
be-student May 3, 2023
4cd768e
feat: 엔티티 작성
be-student May 3, 2023
89bb79e
feat: findByUser 에서 Optional 로 반환 타입 변경
be-student May 3, 2023
642a295
feat: cartDao 작성
be-student May 3, 2023
b8c97f6
feat: productDao 작성
be-student May 3, 2023
2ec77b5
feat: h2cartRepository 탁성
be-student May 3, 2023
a01df7f
chore: nullable 하지 않은 타입에서 Optional.of 를 사용하도록 변경
be-student May 3, 2023
1f51d38
feat: cartMapper 작성
be-student May 3, 2023
b967418
feat: settings.html 작성
be-student May 3, 2023
b7c9847
refactor: dto product 로 변경
be-student May 3, 2023
1fc25d3
feat: user findAll 기능 추가
be-student May 3, 2023
ff5539b
feat: 컨트롤러로 데이터 넘기는 과정 추가
be-student May 3, 2023
8b954f4
chore: 의미 없는 테스트 제거
be-student May 3, 2023
0bcd865
fix: id 가 무조건 1로 테스트 되어있던 부분 제거
be-student May 3, 2023
e884086
feat: 처음에 데이터 심는 과정 추가
be-student May 3, 2023
20d9b71
fix: sql 실행으로 깨지는 테스트 수정
be-student May 3, 2023
d840be5
fix: dao 에서 에러 나는 부분 수정
be-student May 3, 2023
ee68eab
feat: user 가입을 event 로 처리하도록 변경
be-student May 3, 2023
db7492a
feat: cartApiController 작성
be-student May 3, 2023
34a655b
test: dao 테스트 작성
be-student May 3, 2023
82d4950
fix: 깨진 테스트 수정
be-student May 3, 2023
55c7c84
feat: cartjs 작성
be-student May 3, 2023
e5dba3b
feat: 목 데이터 준비
be-student May 3, 2023
b5fe4d8
feat: 커스텀 예외 추가
be-student May 3, 2023
bb1531d
feat: equals 재정의
be-student May 3, 2023
999125a
feat: dto 작성
be-student May 3, 2023
a870ceb
feat: entity 작성
be-student May 3, 2023
a71e264
feat: 상품 추가 기능 추가
be-student May 3, 2023
f3451b1
fix: 상품 html 수정
be-student May 3, 2023
74680f9
feat: delete 기능 구현
be-student May 3, 2023
fe710f3
feat: 필터 적용
be-student May 3, 2023
18f27f6
refactor: 사용하지 않는 update 메서드 제거
be-student May 3, 2023
0738913
feat: 장바구니에 같은 상품 담았을 때 커스텀 예외로 변경
be-student May 4, 2023
8a4689e
feat: errorResponse 제작
be-student May 4, 2023
cffa017
feat: 예외 발생시 경고 메시지 추가
be-student May 4, 2023
faa2692
feat: h2CartRepository 테스트 추가
be-student May 4, 2023
67e6752
feat: stub 객체 추가
be-student May 4, 2023
cb45b9f
test: userCommandService 테스트 작성
be-student May 4, 2023
a0d7783
chore: 메서드 네이밍 findAll 로 변경
be-student May 4, 2023
2e3ddd8
test: userQueryService 테스트 작성
be-student May 4, 2023
ba3cd1c
test: CartCommandService 테스트 작성
be-student May 4, 2023
905942e
chore: 개행 변경
be-student May 4, 2023
fbb10cc
test: 같은 상품 추가시 에러 추가
be-student May 4, 2023
e343cd8
chore: 개행 처리 추가
be-student May 4, 2023
6114e20
docs: 기능 목록 최신화
be-student May 4, 2023
918c63b
feat: 장바구니에 있는 상품 제거시 예외
be-student May 4, 2023
cb80b50
feat: 파라미터 타입 검증에 대한 부분 추가
be-student May 7, 2023
9e597e8
refactor: cart 에서 직접 User 참조를 하지 않고, cartId 만을 사용하도록 변경
be-student May 7, 2023
48a227f
fix: isInstance 대신 isAssignableFrom 형태로 변경
be-student May 7, 2023
bbdb7a2
feat: product과 cart 간의 연관은 id 만으로 변경
be-student May 7, 2023
db89307
refactor: 계층형으로 auth 패키지 변경
be-student May 7, 2023
c684a44
refactor: entity 에 id 를 중복 구현했던 부분 제거
be-student May 7, 2023
d275899
refactor: event 내부에서 userId 만을 저장하도록 변경
be-student May 7, 2023
7790805
feat: controller url 변경
be-student May 8, 2023
b5bfe5f
feat: truncate 를 할 때, h2 일때만 작동하도록 변경
be-student May 8, 2023
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
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,50 @@
# jwp-shopping-cart

Choose a reason for hiding this comment

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

File changed 104 good


## 기능 목록 Step2

- [x] settings 페이지 연동
- [x] settings.html 변경
- [x] 장바구니 페이지 연동
- [x] cart.html 변경

- [x] DB 설계

- [x] 필터 작성

- [x] User 도메인 추가
- [x] User 도메인에 Cart 도메인 추가
- [x] Cart 도메인 추가

## 기능목록

- [x] 상품 목록 페이지 연동
- [x] index.html 변경
- [x] 상품 관리 CRUD API 작성
- [x] create product
- [x] read product
- [x] update product
- [x] delete product
- [x] 관리자 도구 페이지 연동
- [x] admin.html 변경
- [x] admin.js 변경

Choose a reason for hiding this comment

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

API 설계한거 URL도 같이 적어주면 (내가) 매우 좋을 듯


- [x] DB 설계

# 도메인 구조

```mermaid
graph TD

cart --> user
cart --many--> product
```

# DB 설계

```mermaid
graph TD

cart --> user
cart --many--> cart_product
product --many--> cart_product
```
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-validation'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:4.4.0'
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/cart/JwpCartApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
@SpringBootApplication
public class JwpCartApplication {

public static void main(String[] args) {
public static void main(final String[] args) {

Choose a reason for hiding this comment

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

이걸 일케까지??
자동설정인가??

Copy link
Author

Choose a reason for hiding this comment

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

어 자동 설정

Choose a reason for hiding this comment

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

final 붙인거갖고 그러는거야ㅕ?

SpringApplication.run(JwpCartApplication.class, args);
}

}
34 changes: 34 additions & 0 deletions src/main/java/cart/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package cart.config;

import cart.filter.AuthenticationFilter;
import cart.repository.user.UserRepository;
import cart.resolver.AuthenticationResolver;
import java.util.List;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SecurityConfig implements WebMvcConfigurer {

Choose a reason for hiding this comment

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

옼 이름 좀 간지 GDD


private final UserRepository userRepository;

public SecurityConfig(final UserRepository userRepository) {
this.userRepository = userRepository;
}

@Bean
public FilterRegistrationBean<AuthenticationFilter> filterRegistrationBean() {
final FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new AuthenticationFilter(userRepository));
registrationBean.addUrlPatterns("/carts/*, /products/*");
return registrationBean;
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new AuthenticationResolver());
}
}
68 changes: 68 additions & 0 deletions src/main/java/cart/controller/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package cart.controller;

import cart.dto.ErrorResponse;
import cart.exception.AlreadyAddedProductException;
import cart.exception.CartNotFoundException;
import cart.exception.ProductInCartDeleteException;
import cart.exception.ProductNotFoundException;
import cart.exception.UserNotFoundException;
import java.util.Map;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

private final Logger log = LoggerFactory.getLogger(getClass());

Choose a reason for hiding this comment

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

이렇게 로그 찍으면 어디서 어떻게 확인할 수 있어??


@ExceptionHandler
private ResponseEntity<ErrorResponse> handleException(final Exception exception) {
log.error("예상치 못한 예외가 발생했습니다.", exception);

Choose a reason for hiding this comment

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

로그는 개발자가 보는거라 그대로 exception을 내보내도 되는거야??

return ResponseEntity.internalServerError().body(new ErrorResponse("예상치 못한 예외가 발생했습니다."));
}

@ExceptionHandler
private ResponseEntity<ErrorResponse> handleIllegalArgumentException(final IllegalArgumentException exception) {
log.warn("잘못된 인자가 들어왔습니다", exception);
return ResponseEntity.badRequest().body(new ErrorResponse(exception.getMessage()));
}

@ExceptionHandler({CartNotFoundException.class, ProductNotFoundException.class, UserNotFoundException.class})
private ResponseEntity<ErrorResponse> handleNotFoundException(final Exception exception) {
log.warn("존재하지 않는 리소스에 접근했습니다.", exception);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorResponse(exception.getMessage()));
}

@ExceptionHandler
private ResponseEntity<ErrorResponse> handleAlreadyAddedProduct(final AlreadyAddedProductException e) {
log.warn("이미 장바구니에 담긴 상품입니다.", e);
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
}

@ExceptionHandler
private ResponseEntity<ErrorResponse> handleProductInCartDelete(final ProductInCartDeleteException e) {
log.warn("장바구니에 담긴 상품을 삭제할 수 없습니다.", e);
return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage()));
}

@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
final MethodArgumentNotValidException exception, final HttpHeaders headers, final HttpStatus status,
final WebRequest request) {
log.warn("유효성 검사에 실패했습니다.", exception);
final Map<String, String> body = exception.getFieldErrors()
.stream()
.collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
return handleExceptionInternal(exception, body, headers, status, request);
}
}
62 changes: 62 additions & 0 deletions src/main/java/cart/controller/cart/CartApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cart.controller.cart;

import cart.domain.cart.Cart;
import cart.domain.product.Product;
import cart.dto.cart.AddCartResponse;
import cart.dto.cart.AddProductRequest;
import cart.dto.cart.FindCartResponse;
import cart.resolver.AuthInfo;
import cart.resolver.Authentication;
import cart.service.cart.CartCommandService;
import cart.service.cart.CartQueryService;
import cart.service.product.ProductQueryService;
import java.util.List;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/carts/cartproduct")

Choose a reason for hiding this comment

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

cart-product 어떰? ㅋㅋㅋㅎㅎ

public class CartApiController {

private final CartCommandService cartCommandService;
private final ProductQueryService productQueryService;
private final CartQueryService cartQueryService;

public CartApiController(final CartCommandService cartCommandService,
final ProductQueryService productQueryService,
final CartQueryService cartQueryService) {
this.cartCommandService = cartCommandService;
this.productQueryService = productQueryService;
this.cartQueryService = cartQueryService;
}

@PostMapping
public ResponseEntity<AddCartResponse> addProduct(@RequestBody final AddProductRequest addProductRequest,
@Authentication final AuthInfo authInfo) {
final Long productId = addProductRequest.getProductId();
final String email = authInfo.getEmail();
final Cart cart = cartCommandService.addProduct(productId, email);
return ResponseEntity.ok(AddCartResponse.from(cart));
}

@GetMapping
public ResponseEntity<FindCartResponse> findCart(@Authentication final AuthInfo authInfo) {
final Cart cart = cartQueryService.findByEmail(authInfo.getEmail());
final List<Product> products = productQueryService.findAllByIds(cart.getCartProducts().getProductIds());
return ResponseEntity.ok(FindCartResponse.of(cart, products));
}

@DeleteMapping("/{cartProductId}")
public ResponseEntity<Void> deleteProduct(@Authentication final AuthInfo authInfo,
@PathVariable final Long cartProductId) {
final String email = authInfo.getEmail();
cartCommandService.deleteProduct(cartProductId, email);
return ResponseEntity.noContent().build();
}
}
13 changes: 13 additions & 0 deletions src/main/java/cart/controller/cart/CartViewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cart.controller.cart;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class CartViewController {

@GetMapping("/cart")
public String getCartPage() {
return "cart";
}
}
73 changes: 73 additions & 0 deletions src/main/java/cart/controller/product/ProductApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package cart.controller.product;

import cart.domain.product.Product;
import cart.dto.product.ProductRequest;
import cart.dto.product.ProductResponse;
import cart.service.product.ProductCommandService;
import cart.service.product.ProductQueryService;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
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.RestController;

@RestController
public class ProductApiController {

Choose a reason for hiding this comment

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

@RequestMapping("/products") 넣고 밑에서 다 빼는건 어떨까?


private final ProductCommandService productCommandService;
private final ProductQueryService productQueryService;

public ProductApiController(final ProductCommandService productCommandService,
final ProductQueryService productQueryService) {
this.productCommandService = productCommandService;
this.productQueryService = productQueryService;
}

@PostMapping("/products")
public ResponseEntity<ProductResponse> createProduct(@RequestBody @Valid final ProductRequest productRequest) {
final Product product = productCommandService.create(
productRequest.getName(),
productRequest.getImage(),
productRequest.getPrice());

final ProductResponse productResponse = ProductResponse.from(product);
return ResponseEntity.created(URI.create("/products/" + product.getProductId()))

Choose a reason for hiding this comment

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

리소스 위치까지 반환 굳

.body(productResponse);
}

@GetMapping("/products")
public ResponseEntity<List<ProductResponse>> getAllProducts() {
final List<ProductResponse> productResponses = productQueryService.findAll()
.stream()
.map(ProductResponse::from)
.collect(Collectors.toList());

return ResponseEntity.ok(productResponses);
}

@PutMapping("/products/{id}")
public ResponseEntity<ProductResponse> updateProduct(@PathVariable final long id,
@RequestBody @Valid final ProductRequest productRequest) {
final Product product = productCommandService.update(
id,
productRequest.getName(),
productRequest.getImage(),
productRequest.getPrice());

final ProductResponse productResponse = ProductResponse.from(product);
return ResponseEntity.ok(productResponse);
}

@DeleteMapping("/products/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable final long id) {
productCommandService.delete(id);
return ResponseEntity.noContent().build();
}
}
42 changes: 42 additions & 0 deletions src/main/java/cart/controller/product/ProductViewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cart.controller.product;

import cart.dto.product.ProductResponse;
import cart.service.product.ProductQueryService;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ProductViewController {

private final ProductQueryService productSearchService;

public ProductViewController(final ProductQueryService productSearchService) {
this.productSearchService = productSearchService;
}

@GetMapping("/")
public String getIndexPage(final Model model) {
final List<ProductResponse> productResponses = productSearchService.findAll()
.stream()
.map(ProductResponse::from)
.collect(Collectors.toList());

model.addAttribute("products", productResponses);
return "index";
}

@GetMapping("/admin")
public String getAdminPage(final Model model) {
final List<ProductResponse> productResponses = productSearchService.findAll()
.stream()
.map(ProductResponse::from)
.collect(Collectors.toList());

model.addAttribute("products", productResponses);
return "admin";
}

}
30 changes: 30 additions & 0 deletions src/main/java/cart/controller/setting/SettingViewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cart.controller.setting;

import cart.dto.user.UserResponse;
import cart.service.user.UserQueryService;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SettingViewController {

private final UserQueryService userQueryService;

public SettingViewController(final UserQueryService userQueryService) {
this.userQueryService = userQueryService;
}

@GetMapping("/settings")
public String getSettingsPage(final Model model) {
final List<UserResponse> userResponses = userQueryService.findAll()
.stream()
.map(UserResponse::from)
.collect(Collectors.toList());

model.addAttribute("members", userResponses);
return "settings";
}
}
Loading