-
Notifications
You must be signed in to change notification settings - Fork 0
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
피자집 창업 준비 1일차 #6
base: main
Are you sure you want to change the base?
Conversation
* docs: 요구사항 정의 Co-authored-by: greeng00se <[email protected]> * feat: 상품 도메인 추가 Co-authored-by: greeng00se <[email protected]> * feat: db 테이블 추가 Co-authored-by: greeng00se <[email protected]> * feat: Dao 상품 저장 및 전체 조회 기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 서비스 상품 저장 기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 페이지 접근 기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 데이터베이스 설정 추가 Co-authored-by: greeng00se <[email protected]> * feat: 상품 저장 기능 추가 Co-authored-by: greeng00se <[email protected]> * docs: 요구사항 체크 Co-authored-by: greeng00se <[email protected]> * feat: 서비스 상품 조회 기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 상품 조회 기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 상품 수정 기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 상품 삭제 기능 추가 Co-authored-by: greeng00se <[email protected]> * refactor: RestController, Controller 분리 Co-authored-by: greeng00se <[email protected]> * feat: Thymeleaf 단일 상품 조회 페이지 추가 Co-authored-by: greeng00se <[email protected]> * feat: 단일 조회 기능 구현 Co-authored-by: greeng00se <[email protected]> * test: 테스트 중복 제거 및 위치 변경 Co-authored-by: greeng00se <[email protected]> * feat: 저장시 location header 반환기능 추가 Co-authored-by: greeng00se <[email protected]> * feat: 예외처리 기능 추가 Co-authored-by: greeng00se <[email protected]> * refactor: 오타 수정 Co-authored-by: greeng00se <[email protected]> * refactor: 사용하지 않는 DTO 클래스 삭제 Co-authored-by: greeng00se <[email protected]> * refactor: 예외메시지 수정 Co-authored-by: greeng00se <[email protected]> * feat: 이미지 검증 기능 추가 * refactor: 모든 RequestDto -> Request 클래스명 변경 * refactor: 필요없는 Optional 제거 * feat: 커스텀 예외 적용 * feat: 엔티티에 audit 기능 추가 * refactor: 컨트롤러 메서드명 변경 * feat: Service에 Transactional 추가 * test: Long.MAX_VALUE 적용! * test: expect 빠진 부분 수정 * refactor: 예약어 대문자로 변경 * test: test 설정 파일 수정 * test: Controller 테스트에서 id를 이용하여 검증하는 방향으로 수정 --------- Co-authored-by: songusika <[email protected]>
- AuthMemberDao -> CredentialDao - 추가로 Credential을 검증하는 과정에서 에러메시지를 조금 더 자세하게 출력하도록 변경
- 올바른 Basic 유형의 헤더(파싱 불가능한 경우) InvalidBasicCredentialException 예외 던지도록 변경
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
태코챗의 아버지 허브 덕분에 잘 쓰고 있습니다.
@RestController | ||
public class ProductController { | ||
|
||
private final ProductService productService; | ||
|
||
public ProductController(final ProductService productService) { | ||
this.productService = productService; | ||
} | ||
|
||
@PostMapping("/products") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
product controller에는 공통적인 url인 /products
를 request mapping으로 안해놓으셨네용
public List<Product> findAllProductByMemberId(final Long memberId) { | ||
final String sql = "SELECT product.id AS id, name, image, price " | ||
+ "FROM cart_product " | ||
+ "LEFT JOIN product " | ||
+ "ON cart_product.product_id = product.id " | ||
+ "WHERE member_id = ?"; | ||
return jdbcTemplate.query(sql, productRowMapper, memberId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메소드 명이랑 쿼리랑 뭔가 매칭이 안되는 느낌? cart까지 조인하는데 좀 그렇네요 저만 그런가요?>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안그래도 저도 이렇게 짜놓고 좀 걸려서 리뷰어한테 여쭤봤는데, 잘 했다고 하셨삼요
public List<ProductDto> findAll() { | ||
return productDao.findAll().stream() | ||
.map(ProductDto::from) | ||
.collect(toUnmodifiableList()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
왜 나는 레벨 1때는 불변 리스트를 열심히 썼는데 2단계와서는 안쓰고 있을까요? 다 까먹었네ㅐ
private Matcher<Object> generateProductPropertiesMatcher( | ||
final Long id, | ||
final String name, | ||
final String image, | ||
final long price | ||
) { | ||
return allOf( | ||
hasProperty("id", is(id)), | ||
hasProperty("name", is(name)), | ||
hasProperty("image", is(image)), | ||
hasProperty("price", is(price)) | ||
); | ||
} | ||
|
||
private Matcher<Object> generateMemberPropertiesMatcher( | ||
final Long id, | ||
final String email, | ||
final String password | ||
) { | ||
return allOf( | ||
hasProperty("id", is(id)), | ||
hasProperty("email", is(email)), | ||
hasProperty("password", is(password)) | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
당신 참 멋져요
void 이메일을_입력받아_동일한_이메일을_가진_사용자를_조회한다() { | ||
// given | ||
final MapSqlParameterSource parameter = new MapSqlParameterSource(); | ||
parameter.addValue("email", "[email protected]"); | ||
parameter.addValue("password", "password"); | ||
memberJdbcInsert.executeAndReturnKey(parameter).longValue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그냥 credentialDao.save() 하면 안되나용??
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class CredentialDao { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
credential dao와 member dao의 차이가 뭐라고 생각하고 나누신건가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
22
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
허브신! 허브신! 🌿
@Override | ||
public boolean supportsParameter(final MethodParameter parameter) { | ||
final boolean hasParameterAnnotation = parameter.hasParameterAnnotation(Auth.class); | ||
final boolean hasCredentialType = Credential.class.isAssignableFrom(parameter.getParameterType()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
상속까지 고려해서 isAssignableFrom
을 사용한거야?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러게요 이게 뭐죠?
final Credential savedCredential = credentialDao.findByEmail(credential.getEmail()) | ||
.orElseThrow(() -> new AuthenticationException("올바르지 않은 이메일입니다. 입력값: " + credential.getEmail())); | ||
|
||
if (credential.isNotSamePassword(savedCredential)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
email
, password
로 바로 찾지 않은 이유는 무엇이죠?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러게요 findByEmailAndPassword 왜 안하셨죠?
@Component | ||
public class CredentialThreadLocal { | ||
|
||
private final ThreadLocal<Credential> local = new ThreadLocal<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ThreadLocal
👍 ThreadLocal
를 사용했을 때 어떤 장점이 있었어?
private void validate(final String email, final String password) { | ||
validateEmail(email); | ||
validatePassword(password); | ||
} | ||
|
||
private void validateEmail(final String email) { | ||
if (email == null || email.isBlank()) { | ||
throw new MemberNotValidException("이메일은 공백일 수 없습니다."); | ||
} | ||
} | ||
|
||
private void validatePassword(final String password) { | ||
if (password == null || password.isBlank()) { | ||
throw new MemberNotValidException("비밀번호는 공백일 수 없습니다."); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VO
를 만들어서 검증 로직을 넘기는건 어떠신가요 허브신 🌿
|
||
@PostMapping | ||
public ResponseEntity<Void> save(@Auth final Credential credential, | ||
@RequestBody final CartProductSaveRequest request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쿼리 파라미터를 활용하는거에 대해서는 어떻게 생각하나용?
@NotBlank(message = "이미지는 공백일 수 없습니다.") | ||
private final String image; | ||
|
||
@Range(message = "가격은 최소 {min}원 이상, {max}원 이하여야 합니다.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
min
, max
속성이 없는 것 같은데 🤔
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Transactional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용되지 않는 어노테이션은 제거하면 어떨까?
@@ -0,0 +1,13 @@ | |||
INSERT INTO PRODUCT (name, image, price) | |||
VALUES ('피자스쿨 치즈피자', 'https://t1.daumcdn.net/cfile/tistory/2647BE3754B7E8B733', 8900); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
피즈스쿨 치즈피자 얼마전에 먹었는데 존맛 🍕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
허브 피자 사줘
@Override | ||
public boolean supportsParameter(final MethodParameter parameter) { | ||
final boolean hasParameterAnnotation = parameter.hasParameterAnnotation(Auth.class); | ||
final boolean hasCredentialType = Credential.class.isAssignableFrom(parameter.getParameterType()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러게요 이게 뭐죠?
final WebDataBinderFactory binderFactory | ||
) { | ||
final Credential credential = credentialThreadLocal.get(); | ||
credentialThreadLocal.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
threadLocal 신기하네요
final Credential savedCredential = credentialDao.findByEmail(credential.getEmail()) | ||
.orElseThrow(() -> new AuthenticationException("올바르지 않은 이메일입니다. 입력값: " + credential.getEmail())); | ||
|
||
if (credential.isNotSamePassword(savedCredential)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러게요 findByEmailAndPassword 왜 안하셨죠?
.orElseThrow(() -> new AuthenticationException("올바르지 않은 이메일입니다. 입력값: " + credential.getEmail())); | ||
|
||
if (credential.isNotSamePassword(savedCredential)) { | ||
throw new AuthenticationException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
얘도 예외 메시지 지정해주는 거 어떨까요?
|
||
@Component | ||
public class BasicAuthorizationParser { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parser를 빈으로 등록한 이유가 뭐죠?
public List<Product> findAllProductByMemberId(final Long memberId) { | ||
final String sql = "SELECT product.id AS id, name, image, price " | ||
+ "FROM cart_product " | ||
+ "LEFT JOIN product " | ||
+ "ON cart_product.product_id = product.id " | ||
+ "WHERE member_id = ?"; | ||
return jdbcTemplate.query(sql, productRowMapper, memberId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
안그래도 저도 이렇게 짜놓고 좀 걸려서 리뷰어한테 여쭤봤는데, 잘 했다고 하셨삼요
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class CredentialDao { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
22
|
||
import cart.exception.MemberNotValidException; | ||
|
||
public class Member { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번에 entity를 따로 만들지 않은 이유는 뭐죠?
logger.warn(errorMessage); | ||
return ResponseEntity.badRequest() | ||
.body(new ExceptionResponse(errorMessage)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
세세한 예외처리 멋져요
public void update(final Long id, final ProductUpdateRequest request) { | ||
final Product savedProduct = new Product(id, request.getName(), request.getImage(), request.getPrice()); | ||
final int affectedCount = productDao.update(savedProduct); | ||
if (affectedCount == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서오릉 피자의 창업 비용은 8,812만원입니다.