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

내 장바구니에는 농담곰 100개를 넣을거야 #8

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7f1e545
[1단계 - 상품 관리 기능] 오잉(이하늘) 미션 제출합니다. (#183)
hanueleee Apr 29, 2023
8f50529
docs: step2 기능 목록
hanueleee May 3, 2023
3a0b3f8
feat: 사용자 기능 구현
hanueleee May 3, 2023
41fe890
feat: schema와 data분리 및 member테이블 추가
hanueleee May 3, 2023
fa370e7
feat: /settings로 접근할 경우 모든 사용자의 정보를 확인할 수 있는 기능 구현
hanueleee May 3, 2023
ed53a6b
feat: /settings url과 settings.html 연동
hanueleee May 3, 2023
db9d90b
feat: cart 테이블 추가
hanueleee May 3, 2023
228e49d
feat: Authrization 헤더 추출하는 기능 구현
hanueleee May 3, 2023
a492491
feat: 이메일과 패스워드로 memberId를 찾는 기능 구현
hanueleee May 3, 2023
f955e81
feat: 장바구니에 상품을 추가하는 기능 구현
hanueleee May 3, 2023
cf85243
feat: 장바구니 목록을 조회하는 기능 구현
hanueleee May 3, 2023
8d26c34
feat: 장바구니에 담김 상품을 제거하는 기능 구현
hanueleee May 3, 2023
50f2d2b
feat: 장바구니 페이지 연동
hanueleee May 3, 2023
d3fcaad
test: CartController에 대한 테스트 작성
hanueleee May 3, 2023
55ec7c5
refactor: DROP 먼저하고 CREATE
hanueleee May 3, 2023
fc56438
refactor: login 관련 처리를 interceptor에서 하도록 수정
hanueleee May 3, 2023
31ce858
chore: 줄바꿈 정리
hanueleee May 3, 2023
fdd28a6
refactor: 장바구니 상품 추가 api와 장바구니 상품 삭제 api의 상태코드 수정
hanueleee May 3, 2023
39805cc
refactor(productDao): update와 delete의 반환값을 void로 수정
hanueleee May 6, 2023
2815731
refactor(controllerAdvice): 4xx 에러 발생시의 로그 레벨을 error에서 info로 수정
hanueleee May 6, 2023
1b7de2d
refactor(cartController): url 형식을 cart에서 carts로 수정
hanueleee May 6, 2023
195e2f1
refactor(cartController): 장바구니에 상품 추가 및 상품 삭제 api 형식 수정
hanueleee May 6, 2023
02cf75b
refactor(cartController): findAllProductInCart의 반환값 구체적으로 수정
hanueleee May 6, 2023
14caa3d
refactor(entity): id를 int에서 Integer로 수정
hanueleee May 6, 2023
2238a07
refactor(dto): null이 될 수 있는 필드들 래퍼 클래스로 수정
hanueleee May 6, 2023
dd84b6c
refactor: AuthorizationExtractor를 인터페이스화
hanueleee May 6, 2023
b53fb4a
refactor: 인증 관련 예외처리
hanueleee May 6, 2023
fc919cb
refactor: cart테이블 PK와 FK설정
hanueleee May 6, 2023
cee8d87
refactor: 회원 인증을 처리하는 interceptor를 제거하고 argument resolver로 대체
hanueleee May 6, 2023
f3e9e2a
refactor: CartControllerTest 수정
hanueleee May 6, 2023
ff52547
fix(schema): cascade 옵션을 통한 참조 무결성 제약 조건 위반 문제 해결
hanueleee May 6, 2023
0a2cec8
refactor(dto): dto명 수정
hanueleee May 6, 2023
74d207d
refactor(controller): body가 없는경우 ResponseEntity<Void>로 수정
hanueleee May 8, 2023
bc4c973
refactor(argumentResolver): supportsParamter 보완
hanueleee May 9, 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
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,41 @@
# jwp-shopping-cart

## 기능 목록
### step1
- [x] 상품 목록 페이지 연동
- [x] index.html 파일 내 TODO 주석을 참고하여 설계한 상품 정보에 맞게 코드를 변경
- [x] index.html 파일을 이용하여 상품 목록이 노출되는 페이지를 완성
- [x] '/' url로 접근할 경우 상품 목록 페이지를 조회
- [x] 상품 기본 정보 : 상품 ID, 상품 이름, 상품 이미지, 상품 가격

- [x] 상품 관리 CRUD API 작성
- [x] Create : POST - /admin
- [x] Read : GET - /admin
- [x] Update : POST - /admin/{id}
- [x] Delete : DELETE - /admin/{id}

- [x] 관리자 도구 페이지 연동
- [x] admin.html, admin.js 파일 내 TODO 주석을 참고하여 코드를 변경
- [x] admin.html 파일과 상품 관리 CRUD API를 이용하여 상품 관리 페이지를 완성
- [x] '/admin' url로 접근할 경우 관리자 도구 페이지를 조회

### step2
- [x] 사용자 기능 구현
- [x] 사용자가 가지고 있는 정보 : email, password

- [x] 사용자 설정 페이지 연동
- [x] settings.html, settings.js 파일 내 TODO 주석을 참고하여 설계한 사용자 정보에 맞게 코드를 변경
- [x] settings.html 파일을 이용해서 사용자를 선택하는 기능을 구현
- [x] /settings url로 접근할 경우 모든 사용자의 정보를 확인하고 사용자를 선택 가능

- [x] 인증 관련
- [x] 사용자 설정 페이지에서 사용자를 선택하면, 이후 요청에 선택한 사용자의 인증 정보가 포함
- [x] 사용자 정보는 요청 Header의 Authorization 필드를 사용해 인증 처리를 하여 획득
- [x] 인증 방식은 Basic 인증 사용

- [x] 장바구니 기능 구현
- [x] 장바구니에 상품 추가
- [x] 장바구니에 담긴 상품 제거
- [x] 장바구니 목록 조회

- [x] 장바구니 페이지 연동
Comment on lines +3 to +41
Copy link
Member

Choose a reason for hiding this comment

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

기능 목록 곰곰 👍

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
53 changes: 53 additions & 0 deletions src/main/java/cart/controller/CartController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cart.controller;

import cart.dto.CartProductAddRequest;
import cart.dto.CartProductRemoveRequest;
import cart.dto.ProductDto;
import cart.service.CartService;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
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.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")
public class CartController {

private final CartService cartService;

public CartController(CartService cartService) {
this.cartService = cartService;
}

@GetMapping()
public ResponseEntity<List<ProductDto>> findAllProductInCart(HttpServletRequest request) {
int memberId = (int) request.getAttribute("memberId");

List<ProductDto> allProduct = cartService.findAllProduct(memberId);
return ResponseEntity.status(HttpStatus.OK).body(allProduct);
}

@PostMapping()
public ResponseEntity<Object> addProductToCart(HttpServletRequest request,
@RequestBody CartProductAddRequest cartProductAddRequest) {
int memberId = (int) request.getAttribute("memberId");

cartService.addProduct(memberId, cartProductAddRequest.getProductId());
return ResponseEntity.status(HttpStatus.CREATED).build();

Choose a reason for hiding this comment

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

CREATED를 사용할 때는 Location 헤더에 자원에 대한 정보를 주는건 어때?

}

@DeleteMapping()
public ResponseEntity<Object> removeProductFromCart(HttpServletRequest request,
@RequestBody CartProductRemoveRequest cartProductRemoveRequest) {
int memberId = (int) request.getAttribute("memberId");

cartService.deleteProduct(memberId, cartProductRemoveRequest.getProductId());
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}
42 changes: 42 additions & 0 deletions src/main/java/cart/controller/CartControllerAdvice.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cart.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class CartControllerAdvice {

public static final String SERVER_ERROR = "알 수 없는 에러가 발생했습니다.";

Choose a reason for hiding this comment

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

public으로 열려있는 이유가 무엇이죠?!

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

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String errorMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
log.info(errorMessage);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorMessage);
}

@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.info(e.getMessage());
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).body(e.getMessage());
}

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
log.info(e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
Comment on lines +18 to +35
Copy link
Member

Choose a reason for hiding this comment

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

예외의 대한 로깅은 info보다 warn으로 조정해보는건 어떨까?


@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleServerException(Exception e) {
log.error(e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(SERVER_ERROR);
}
}
46 changes: 46 additions & 0 deletions src/main/java/cart/controller/ProductController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cart.controller;

import cart.dto.ProductAddRequest;
import cart.dto.ProductModifyRequest;
import cart.service.ProductService;
import java.net.URI;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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;

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

private final ProductService productService;

public ProductController(ProductService productService) {
this.productService = productService;
}

@PostMapping()

Choose a reason for hiding this comment

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

빈 괄호 불편해용~

public ResponseEntity<Integer> productAdd(@Validated @RequestBody ProductAddRequest productAddRequest) {
int productId = productService.save(productAddRequest);
return ResponseEntity.status(HttpStatus.CREATED).location(URI.create("/products/" + productId)).build();
}

@PutMapping("/{id}")
public ResponseEntity<Object> productModify(@Validated @RequestBody ProductModifyRequest productModifyRequest,
@PathVariable int id) {
productService.update(productModifyRequest, id);
return ResponseEntity.status(HttpStatus.OK).build();
}

@DeleteMapping("/{id}")
public ResponseEntity<Object> productRemove(@PathVariable int id) {
productService.delete(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
}
50 changes: 50 additions & 0 deletions src/main/java/cart/controller/ViewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cart.controller;

import cart.dto.MemberDto;
import cart.dto.ProductDto;
import cart.service.MemberService;
import cart.service.ProductService;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/")
public class ViewController {

private final ProductService productService;
private final MemberService memberService;

public ViewController(ProductService productService, MemberService memberService) {
this.productService = productService;
this.memberService = memberService;
}

@GetMapping()
public String home(Model model) {
List<ProductDto> products = productService.findAll();
model.addAttribute("products", products);
return "index";
}

@GetMapping("admin")
public String productList(Model model) {
List<ProductDto> products = productService.findAll();
model.addAttribute("products", products);
return "admin";
}

@RequestMapping("settings")
public String userSetting(Model model) {
List<MemberDto> members = memberService.findAll();
model.addAttribute("members", members);
return "settings";
}

@RequestMapping("cart")
public String cartList() {
return "cart";
}
}
37 changes: 37 additions & 0 deletions src/main/java/cart/dao/CartDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cart.dao;

import cart.entity.ProductEntity;
import java.util.List;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class CartDao {

private final JdbcTemplate jdbcTemplate;

public CartDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public void save(int memberId, int productId) {
String sql = "INSERT INTO cart (member_id, product_id) VALUES (?, ?)";
jdbcTemplate.update(sql, memberId, productId);
}

public List<ProductEntity> findAllByMemberId(int memberId) {
String sql = "SELECT id, p.name, p.imgUrl, p.price\n"
+ "FROM cart AS c\n"
+ "INNER JOIN product AS p\n"
+ "ON c.product_id = p.id\n"
+ "WHERE member_id = ?;";
BeanPropertyRowMapper<ProductEntity> mapper = BeanPropertyRowMapper.newInstance(ProductEntity.class);
return jdbcTemplate.query(sql, mapper, memberId);
}

public void delete(int memberId, int productId) {
String sql = "DELETE FROM cart WHERE member_id = ? AND product_id = ?";
jdbcTemplate.update(sql, memberId, productId);
}
}
35 changes: 35 additions & 0 deletions src/main/java/cart/dao/MemberDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cart.dao;

import cart.entity.MemberEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class MemberDao {

private final JdbcTemplate jdbcTemplate;

public MemberDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

public List<MemberEntity> findAll() {
String sql = "SELECT * FROM member";
BeanPropertyRowMapper<MemberEntity> mapper = BeanPropertyRowMapper.newInstance(MemberEntity.class);
return jdbcTemplate.query(sql, mapper);
}

public Optional<MemberEntity> findByEmailAndPassword(String email, String password) {
String sql = "SELECT * FROM member WHERE email = ? AND password = ?";
BeanPropertyRowMapper<MemberEntity> mapper = BeanPropertyRowMapper.newInstance(MemberEntity.class);
try {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, mapper, email, password));
} catch (final EmptyResultDataAccessException e) {
return Optional.empty();
}
Comment on lines +29 to +33
Copy link
Member

Choose a reason for hiding this comment

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

메서드로 분리하면 이쁠거 같아용

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

import cart.entity.ProductEntity;
import java.util.List;
import java.util.Optional;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;

@Repository
public class ProductDao {

private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public ProductDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
}

public int save(ProductEntity productEntity) {

Choose a reason for hiding this comment

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

CartDao에서 save는 void였는데 여기는 int여서 통일시키는것에 대해서는 어떻게 생각하시나요?

GeneratedKeyHolder keyholder = new GeneratedKeyHolder();
String sql = "INSERT INTO product (name, imgUrl, price) VALUES (:name, :imgUrl, :price)";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(productEntity);
namedParameterJdbcTemplate.update(sql, namedParameters, keyholder, new String[]{"id"});

return keyholder.getKey().intValue();
}

public Optional<ProductEntity> findById(int id) {
String sql = "SELECT * FROM product WHERE id = ?";
BeanPropertyRowMapper<ProductEntity> mapper = BeanPropertyRowMapper.newInstance(ProductEntity.class);
try {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, mapper, id));
} catch (final EmptyResultDataAccessException e) {
return Optional.empty();
}
}

public List<ProductEntity> findAll() {
String sql = "SELECT * FROM product";
BeanPropertyRowMapper<ProductEntity> mapper = BeanPropertyRowMapper.newInstance(ProductEntity.class);
return jdbcTemplate.query(sql, mapper);
}

public void update(ProductEntity productEntity) {
String sql = "UPDATE product SET name=:name, imgUrl=:imgUrl, price=:price WHERE id=:id";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(productEntity);
namedParameterJdbcTemplate.update(sql, namedParameters);
}

public void delete(int id) {
String sql = "DELETE FROM product WHERE id=:id";
MapSqlParameterSource mapSqlParameters = new MapSqlParameterSource()
.addValue("id", id);
namedParameterJdbcTemplate.update(sql, mapSqlParameters);
}
}
Loading