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

피자집 창업 준비 1일차 #6

Open
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

greeng00se
Copy link
Member

서오릉 피자의 창업 비용은 8,812만원입니다.

greeng00se and others added 30 commits April 29, 2023 16:56
* 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 예외 던지도록 변경
Copy link
Member

@drunkenhw drunkenhw left a comment

Choose a reason for hiding this comment

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

태코챗의 아버지 허브 덕분에 잘 쓰고 있습니다.

Comment on lines +16 to +25
@RestController
public class ProductController {

private final ProductService productService;

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

@PostMapping("/products")
Copy link
Member

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으로 안해놓으셨네용

Comment on lines +38 to +45
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);
}
Copy link
Member

Choose a reason for hiding this comment

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

메소드 명이랑 쿼리랑 뭔가 매칭이 안되는 느낌? cart까지 조인하는데 좀 그렇네요 저만 그런가요?>

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());
Copy link
Member

Choose a reason for hiding this comment

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

왜 나는 레벨 1때는 불변 리스트를 열심히 썼는데 2단계와서는 안쓰고 있을까요? 다 까먹었네ㅐ

Comment on lines +43 to +67
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))
);
}
Copy link
Member

Choose a reason for hiding this comment

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

당신 참 멋져요

Comment on lines +39 to +44
void 이메일을_입력받아_동일한_이메일을_가진_사용자를_조회한다() {
// given
final MapSqlParameterSource parameter = new MapSqlParameterSource();
parameter.addValue("email", "[email protected]");
parameter.addValue("password", "password");
memberJdbcInsert.executeAndReturnKey(parameter).longValue();
Copy link
Member

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 {
Copy link
Member

Choose a reason for hiding this comment

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

credential dao와 member dao의 차이가 뭐라고 생각하고 나누신건가요?

Choose a reason for hiding this comment

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

22

Copy link

@woo-chang woo-chang left a 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());

Choose a reason for hiding this comment

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

상속까지 고려해서 isAssignableFrom을 사용한거야?

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)) {

Choose a reason for hiding this comment

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

email, password로 바로 찾지 않은 이유는 무엇이죠?

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<>();

Choose a reason for hiding this comment

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

ThreadLocal 👍 ThreadLocal를 사용했을 때 어떤 장점이 있었어?

Comment on lines +22 to +37
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("비밀번호는 공백일 수 없습니다.");
}
}

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) {

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}원 이하여야 합니다.")

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

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);

Choose a reason for hiding this comment

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

피즈스쿨 치즈피자 얼마전에 먹었는데 존맛 🍕

woo-chang

This comment was marked as duplicate.

Copy link

@hanueleee hanueleee left a 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());

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();

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)) {

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();

Choose a reason for hiding this comment

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

얘도 예외 메시지 지정해주는 거 어떨까요?


@Component
public class BasicAuthorizationParser {

Choose a reason for hiding this comment

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

parser를 빈으로 등록한 이유가 뭐죠?

Comment on lines +38 to +45
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);
}

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 {

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 {

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));
}

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) {

Choose a reason for hiding this comment

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

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants