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

[2단계 - 장바구니 기능] 저문(유정훈) 미션 제출합니다. #333

Merged
merged 26 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
54ae35c
refactor: Product의 Response를 담당하는 DTO의 생성자를 private으로 변경 및 클래스명 변경
jeomxon Apr 29, 2023
07c95e5
refactor: data.sql의 테이블 컬럼 타입 변경
jeomxon Apr 30, 2023
e27f856
refactor: ProductRequest의 필드 값에 대한 검증 변경
jeomxon Apr 30, 2023
ad5e2ca
feat: member테이블 생성 쿼리 작성
jeomxon May 2, 2023
08085c2
feat: 사용자 전체를 불러오는 기능 구현
jeomxon May 2, 2023
14852a7
feat: 사용자 더미 데이터를 위한 쿼리 추가
jeomxon May 2, 2023
720b6c2
feat: 장바구니에 대응하는 cart테이블 구현
jeomxon May 3, 2023
4ced994
feat: Basic인증에서 사용자 정보를 추출하는 기능 구현
jeomxon May 3, 2023
61b7acd
feat: 장바구니 페이지 조회 기능 구현
jeomxon May 3, 2023
8bea763
feat: 장바구니 상품 추가 기능 구현
jeomxon May 4, 2023
9ce5944
feat: 장바구니 상품 삭제 기능 구현
jeomxon May 4, 2023
37c43fe
fix: 프로덕션 코드 변경에 따른 테스트 실패 보완
jeomxon May 5, 2023
5a59881
refactor: CartDao의 save메서드가 저장된 id를 반환하도록 수정
jeomxon May 5, 2023
96da3f6
test: 등록된 모든 멤버를 찾는 검증 추가
jeomxon May 5, 2023
87cc205
test: CartDao에 대한 검증 추가
jeomxon May 5, 2023
715c77f
refactor: 인증 관련 패키지 분리
jeomxon May 5, 2023
f3e9486
refactor: POST를 반환하는 API에 대해 성공 시 201을 반환하도롹 변경
jeomxon May 5, 2023
85a4a9b
feat: AuthInterceptor 및 exception 추가
jeomxon May 7, 2023
1889b6f
feat: AuthArgumentResolver를 추가하여 Controller의 중복코드 제거
jeomxon May 7, 2023
94c19a4
chore: 불필요한 TODO주석 제거
jeomxon May 7, 2023
ad9eabb
refactor: RestController와 Controller를 분리
jeomxon May 7, 2023
7773474
test: PageController에 대한 검증 추가
jeomxon May 8, 2023
7de5726
chore: config패키지 분리
jeomxon May 8, 2023
14be0ba
refactor: 반환되는 status 및 일부 코드 스타일 수정
jeomxon May 8, 2023
7336523
test: CartController의 REST요청에 대한 검증 추가
jeomxon May 8, 2023
f636b28
refactor: CartResponse의 필드 변경
jeomxon May 14, 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
11 changes: 11 additions & 0 deletions src/main/java/cart/auth/Auth.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cart.auth;

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 Auth {
}
35 changes: 35 additions & 0 deletions src/main/java/cart/auth/AuthArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cart.auth;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
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;

@Component
public class AuthArgumentResolver implements HandlerMethodArgumentResolver {

private final BasicAuthorizationExtractor basicAuthorizationExtractor;

public AuthArgumentResolver(final BasicAuthorizationExtractor basicAuthorizationExtractor) {
this.basicAuthorizationExtractor = basicAuthorizationExtractor;
}

@Override
public boolean supportsParameter(final MethodParameter parameter) {
return parameter.hasParameterAnnotation(Auth.class);
}

@Override
public AuthInfo resolveArgument(final MethodParameter parameter,
final ModelAndViewContainer mavContainer,
final NativeWebRequest webRequest,
final WebDataBinderFactory binderFactory) {
String header = webRequest.getHeader(HttpHeaders.AUTHORIZATION);
AuthInfo authInfo = basicAuthorizationExtractor.extract(header);

return authInfo;
}
}
20 changes: 20 additions & 0 deletions src/main/java/cart/auth/AuthInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cart.auth;

public class AuthInfo {

private final String email;
private final String password;

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

public String getEmail() {
return email;
}

public String getPassword() {
return password;
}
}
24 changes: 24 additions & 0 deletions src/main/java/cart/auth/AuthInterceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cart.auth;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AuthInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(final HttpServletRequest request,
final HttpServletResponse response,
final Object handler) {
String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION);
if (accessToken == null) {
throw new AuthorizationException("인증 정보가 존재하지 않습니다.");
}

return true;
}
}
12 changes: 12 additions & 0 deletions src/main/java/cart/auth/AuthorizationException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package cart.auth;

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

@ResponseStatus(HttpStatus.UNAUTHORIZED)
public class AuthorizationException extends RuntimeException {

public AuthorizationException(final String message) {
super(message);
}
}
31 changes: 31 additions & 0 deletions src/main/java/cart/auth/BasicAuthorizationExtractor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package cart.auth;

import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Component;

@Component
public class BasicAuthorizationExtractor {

private static final String BASIC_TYPE = "Basic";
private static final String DELIMITER = ":";

public AuthInfo extract(final String header) {
if (header == null) {
return null;
}

if ((header.toLowerCase().startsWith(BASIC_TYPE.toLowerCase()))) {
String authHeaderValue = header.substring(BASIC_TYPE.length()).trim();
byte[] decodedBytes = Base64.decodeBase64(authHeaderValue);
String decodedString = new String(decodedBytes);

String[] credentials = decodedString.split(DELIMITER);
String email = credentials[0];
String password = credentials[1];

return new AuthInfo(email, password);
}

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

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import cart.auth.AuthArgumentResolver;
import cart.auth.AuthInterceptor;

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

private final AuthInterceptor authInterceptor;
private final AuthArgumentResolver authArgumentResolver;

public WebMvcConfiguration(final AuthInterceptor authInterceptor, final AuthArgumentResolver authArgumentResolver) {
this.authInterceptor = authInterceptor;
this.authArgumentResolver = authArgumentResolver;
}

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor).addPathPatterns("/cart/products/**");
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(authArgumentResolver);
}
}
32 changes: 0 additions & 32 deletions src/main/java/cart/controller/AdminController.java

This file was deleted.

56 changes: 39 additions & 17 deletions src/main/java/cart/controller/CartController.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,54 @@
package cart.controller;

import cart.controller.dto.ProductDto;
import cart.dao.ProductDao;
import cart.auth.Auth;
import cart.auth.AuthInfo;
import cart.controller.dto.CartResponse;
import cart.controller.dto.ProductResponse;
import cart.dao.CartDao;
import cart.domain.Product;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RestController
@RequestMapping("/cart/products")
public class CartController {

private final ProductDao productDao;
private final CartDao cartDao;

public CartController(final ProductDao productDao) {
this.productDao = productDao;
public CartController(final CartDao cartDao) {
this.cartDao = cartDao;
}

@GetMapping("/")
public String findAllProducts(final Model model) {
List<Product> products = productDao.findAll();
List<ProductDto> productDtos = products.stream()
.map(ProductDto::from)
@PostMapping("/{productId}")
public ResponseEntity<Void> addProduct(@PathVariable final Long productId, @Auth final AuthInfo authInfo) {
cartDao.saveCartItemByMemberEmail(authInfo.getEmail(), productId);

return ResponseEntity.created(URI.create("/cart/products" + productId)).build();
}

@GetMapping
public ResponseEntity<List<ProductResponse>> findAllProductsByMember(@Auth final AuthInfo authInfo) {
List<Product> cartItems = cartDao.findCartItemsByMemberEmail(authInfo.getEmail());
List<ProductResponse> productResponses = cartItems.stream()
.map(ProductResponse::from)
.collect(Collectors.toList());
model.addAttribute("products", productDtos);
CartResponse cartResponse = new CartResponse(productResponses);

return ResponseEntity.ok().body(cartResponse.getProductResponses());
}

@DeleteMapping("/{cartId}")
public ResponseEntity<Void> removeProduct(@PathVariable final Long cartId) {
cartDao.deleteCartItemById(cartId);

return "index";
return ResponseEntity.noContent().build();
}
}
65 changes: 65 additions & 0 deletions src/main/java/cart/controller/PageController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package cart.controller;

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;

import cart.controller.dto.MemberResponse;
import cart.controller.dto.ProductResponse;
import cart.dao.MemberDao;
import cart.dao.ProductDao;
import cart.domain.Member;
import cart.domain.Product;

@Controller
public class PageController {

private final ProductDao productDao;
private final MemberDao memberDao;

public PageController(final ProductDao productDao, final MemberDao memberDao) {
this.productDao = productDao;
this.memberDao = memberDao;
}

@GetMapping("/")
public String index(final Model model) {
List<Product> products = productDao.findAll();
List<ProductResponse> productResponses = products.stream()
.map(ProductResponse::from)
.collect(Collectors.toList());
model.addAttribute("products", productResponses);

return "index";
}

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

return "admin";
}

@GetMapping("/settings")
public String settings(final Model model) {
List<Member> members = memberDao.findAll();
List<MemberResponse> memberResponse = members.stream()
.map(member -> new MemberResponse(member.getEmail(), member.getPassword()))
.collect(Collectors.toList());
model.addAttribute("members", memberResponse);

return "settings";
}

@GetMapping("/cart")
public String cart() {
return "cart";
}
}
24 changes: 16 additions & 8 deletions src/main/java/cart/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package cart.controller;

import cart.controller.dto.ProductRequest;
import cart.dao.ProductDao;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.net.URI;

import javax.validation.Valid;

@Controller
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
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;

import cart.controller.dto.ProductRequest;
import cart.dao.ProductDao;

@RestController
@RequestMapping("/products")
public class ProductController {

Expand All @@ -22,7 +30,7 @@ public ProductController(final ProductDao productDao) {
public ResponseEntity<Void> createProduct(@Valid @RequestBody final ProductRequest productRequest) {
productDao.save(productRequest);

return ResponseEntity.ok().build();
return ResponseEntity.created(URI.create("/products")).build();
}

@PutMapping("/{productId}")
Expand All @@ -36,6 +44,6 @@ public ResponseEntity<Void> modifyProduct(@PathVariable final Long productId, @V
public ResponseEntity<Void> removeProduct(@PathVariable final Long productId) {
productDao.deleteById(productId);

return ResponseEntity.ok().build();
return ResponseEntity.noContent().build();
}
}
Loading