Light Blue Pointer
본문 바로가기
Developing/개발일지

2024-01-16, Today I Learned

by Greedy 2024. 1. 16.

오늘 한 일

1. 리뷰 백엔드 개발

2. 글 상세페이지, 작성페이지 로그인값 받아서 연결! → 모종의 이유로 로그인 페이지에서 로그인 성공하고 헤더에 달아둔게 페이지 이동하면 달리지 않아 있어서 인증 실패하는 이슈로 그냥 페이지만 다듬고 말았다 ㅋㅋ

3. 리뷰 파트 테이블 구조 리팩토링 진행! ⛏️

4. 모집마감시 주문최소금액 넘었는지 판별하는 로직 추가

 

1. 리뷰 백엔드 개발

리뷰 백엔드 개발 시작!

 

1-1. 리뷰 쓰려고 주문기록(Order) 조회하는 기능 먼저 만듦

예전에 팀 회의에서 같이 정한 리뷰 선택지

친절하고 매너가 좋아요 35

시간 약속을 잘 지켜요 32

소통과 응답이 빨라요 31

약속 시간에 나타나지 않았어요 17

아예 나타나지 않았어요 12

값을 지불하지 않았어요 1

소통과 응답이 느려요 13

불친절하고 매너가 좋지 않아요 2

이렇게 만들어봄

@Getter
@RequiredArgsConstructor
public enum ReviewEnum {
    //positive
    GOODMANNER("친절하고 매너가 좋아요"),
    GOODTIME("시간 약속을 잘 지켜요"),
    GOODCOMM("소통과 응답이 빨라요"),
    //negative
    BADTIME("약속 시간에 나타나지 않았어요"),
    NOSHOW("아예 나타나지 않았어요"),
    NOMONEY("값을 지불하지 않았어요"),
    BADCOMM("소통과 응답이 느려요"),
    BADMANNER("불친절하고 매너가 좋지 않아요");

    private final String comment;
}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "tb_review")
public class Review extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;
    
    @Column
    private Integer score;

    @Column
    private Integer goodmanner;

    @Column
    private Integer goodtime;

    @Column
    private Integer goodcomm;

    @Column
    private Integer badtime;

    @Column
    private Integer noshow;

    @Column
    private Integer nomoney;

    @Column
    private Integer badcomm;

    @Column
    private Integer badmanner;

}

그냥 해당하는 거 숫자만 가지고 있게 함

Long으로 하려다가 설마 Integer 범위를 넘진 않겠지 싶어서 그냥 Integer로 함

OrderResponse 만듦

@Builder
public record OrderResponse (
    String store,
    String receiverName,
    List<MenuResponse> menus
    
){

}

Controller 만듦

// 리뷰를 작성할 Order들 조회하기
    @GetMapping("/reviews")
    public ApiResponse<List<OrderResponse>> getOrders(
        @AuthenticationPrincipal UserDetailsImpl userDetails
    ) {
        return new ApiResponse<>(HttpStatus.OK.value(), "모든 Order 조회에 성공했습니다.",
            reviewService.getOrders(userDetails.getUser()));
    }

Service 만듦

@Override
    public List<OrderResponse> getOrders(User user) {

        List<Order> orders = orderRepository.findAllByUser(user);
        return orders.stream().map(
            order -> OrderResponse.builder().id(order.getId()).store(order.getStore()).menus(
                    order.getMenus().stream()
                        .map(menu -> new MenuResponse(menu.getMenuname(), menu.getPrice())).toList())
                .receiverName(order.getReceiver().getNickname()).build()).toList();

    }

테스트해봄

{
    "status": 200,
    "message": "모든 Order 조회에 성공했습니다.",
    "data": [
        {
            "id": 1,
            "store": "storestore",
            "receiverName": "유저2",
            "menus": []
        },
        {
            "id": 3,
            "store": "storestore",
            "receiverName": "유저2",
            "menus": []
        },
        {
            "id": 5,
            "store": "storestore",
            "receiverName": "유저2",
            "menus": []
        }
    ]
}

잘 나옴

 

1-2. 리뷰 작성 기능

다음으로 리뷰 작성 기능을 해봄

점수를 어떻게 하지 하다가 그냥 숫자로 입력하면 오타나거나 틀릴 확률이 높아질 거 같아서

Enum을 또 만들어봄,

그리고 평점 계산을 위해 Review에 리뷰 받은 횟수 칼럼 만들기로 함

@Getter
@RequiredArgsConstructor
public enum ScoreEnum {

    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5);
    
    private Integer score;
}

근데 (1) 부분들 다 빨간줄이 쳐진다

@Getter
@RequiredArgsConstructor
public enum ScoreEnum {

    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5);

    private int score;
}

아직도 빨간줄임

@Getter
@RequiredArgsConstructor
public enum ScoreEnum {

    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5);

    private final int score;
    
}

score에 final 붙이니까 사라짐

 

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "tb_review")
public class Review extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @Column
    private Integer score;

    @Column
    private Integer count;

    @Column
    private Integer goodmanner;

    @Column
    private Integer goodtime;

    @Column
    private Integer goodcomm;

    @Column
    private Integer badtime;

    @Column
    private Integer noshow;

    @Column
    private Integer nomoney;

    @Column
    private Integer badcomm;

    @Column
    private Integer badmanner;

}

값을 받아와봄

public record ReviewRequest(
    Long orderId,
    ScoreEnum score,
    boolean goodmanner,
    boolean goodtime,
    boolean goodcomm,
    boolean badtime,
    boolean noshow,
    boolean nomoney,
    boolean badcomm,
    boolean badmanner
) {

}

Controller단 구현

// 리뷰 작성
    @PostMapping("")
    public ApiResponse<Void> review(
        @AuthenticationPrincipal UserDetailsImpl userDetails,
        @RequestBody ReviewRequest reviewReq
    ) {
        reviewService.review(reviewReq,userDetails.getUser());
        return new ApiResponse<>(HttpStatus.OK.value(), "리뷰를 성공적으로 전송했습니다.");
    }

Service단 구현

private Order findOrderById(Long orderId){
        return orderRepository.findById(orderId).orElseThrow(()-> new GlobalException(
            OrderErrorCode.ORDER_NOT_FOUND));
    }

이 부분 이렇게 한번 시도해봄

항상 이런식으로 두 부분으로 나눠서 썼었음

private Order findOrderById(Long orderId){
        Order order =  orderRepository.findById(orderId).orElseThrow(()-> new GlobalException(
            OrderErrorCode.ORDER_NOT_FOUND));
return order;
    }

그리고 나서 로직 짜면서 생각이 드는게, 리뷰를 쓰려는 회원이 이미 탈퇴한 상태면 어떡함

그래서 리뷰를 남길때 회원이 존재하는지를 보는게 좋겠다고 생각함

그래서 존재하는 회원인지 확인하는 메서드 만들었음

존재하는 회원인지 확인하고 나면 reviewRepository 검색해와서 발견되지 않으면 review테이블 생성하면 됨 회원이 존재하지 않아서(탈퇴해서) review Entity도 존재하지 않을 가능성이 없어졌음

리뷰가 만들어져있는 회원이면 그거 쓰고 없으면 만드는 메서드 만들어봄

@RequiredArgsConstructor
@Service
public class ReviewServiceImpl implements ReviewService {

    private final ReviewRepository reviewRepository;
    private final OrderRepository orderRepository;
    private final UserRepository userRepository;

    @Override
    public List<OrderResponse> getOrders(User user) {

        List<Order> orders = orderRepository.findAllByUser(user);
        return orders.stream().map(
            order -> OrderResponse.builder().id(order.getId()).store(order.getStore()).menus(
                    order.getMenus().stream()
                        .map(menu -> new MenuResponse(menu.getMenuname(), menu.getPrice())).toList())
                .receiverName(order.getReceiver().getNickname()).build()).toList();

    }

    @Override
    public void review(ReviewRequest reviewReq, User user) {
        Order order = findOrderById(reviewReq.orderId());
        User receiver = order.getReceiver();
        checkIfUserExists(receiver.getId());
        Review review = findReviewByUser(receiver);
        updateReview(review, reviewReq);
        reviewRepository.save(review);
        orderRepository.delete(order);
    }

    private Order findOrderById(Long orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new GlobalException(OrderErrorCode.ORDER_NOT_FOUND));
    }

    private void checkIfUserExists(Long userId) {
        if (!userRepository.existsById(userId)) {
            throw new GlobalException(UserErrorCode.NOT_EXIST_USER);
        }
    }

    private Review findReviewByUser(User user) {
        //get Review when exists, make a new one when not
        return reviewRepository.findByUser(user).orElse(makeReview(user));
    }

    private Review makeReview(User user) {
        return new Review(user);
    }

    private void updateReview(Review review, ReviewRequest reviewReq) {
        review.increaseScoreAndCount(reviewReq.score());

        if (reviewReq.goodmanner()) {
            review.increaseGoodmanner();
        }
        if (reviewReq.goodcomm()) {
            review.increaseGoodcomm();
        }
        if (reviewReq.goodtime()) {
            review.increaseGoodtime();
        }
        if (reviewReq.badtime()) {
            review.increaseBadtime();
        }
        if (reviewReq.noshow()) {
            review.increaseNoshow();
        }
        if (reviewReq.nomoney()) {
            review.increaseNomoney();
        }
        if (reviewReq.badcomm()) {
            review.increaseBadcomm();
        }
        if (reviewReq.badmanner()) {
            review.increaseBadmanner();
        }
    }

}

ㅋㅋ 퓨어한 노가다의 현장

아무튼 저렇게 짜봤는데

테스트 하려다가 생각해보니 저 리뷰를 남기는게 order의 user인지 확인을 안 함

if(!order.getUser().getId().equals(user.getId())){
            throw new GlobalException(OrderErrorCode.FORBIDDEN_ACCESS);
        }

그래서 이 부분 추가해봄

그리고 테스트 진행해봄

난리남 ㅋㅋㅋㅋㅋㅋㅋㅋ

{
    "orderId" : 1,
    "score" : "FIVE",
    "goodmanner" : "true",
    "badcomm" : "true"
}

2024-01-16T14:06:46.296+09:00 ERROR 11404 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.score" is null] with root cause

java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "this.score" is null

 

@Column
    private Integer score;

    @Column
    private Integer count;

    @Column
    private Integer goodmanner;

    @Column
    private Integer goodtime;

    @Column
    private Integer goodcomm;

    @Column
    private Integer badtime;

    @Column
    private Integer noshow;

    @Column
    private Integer nomoney;

    @Column
    private Integer badcomm;

    @Column
    private Integer badmanner;

Integer는 객체라서 아무것도 안 들어있으면 0이 아니고 null인 상태인거네

생성자에서 0으로 초기화를 하게 함

public Review(User user) {
        this.user = user;
        this.score = 0;
        this.count = 0;
        this.goodmanner = 0;
        this.goodtime = 0;
        this.goodcomm = 0;
        this.badtime = 0;
        this.noshow = 0;
        this.nomoney = 0;
        this.badcomm = 0;
        this.badmanner = 0;
    }

단순노동의 현장 ⛏️

다시 테스트해봄

{
    "status": 200,
    "message": "리뷰를 성공적으로 전송했습니다.",
    "data": null
}

1,2024-01-16 14:13:25.813560,2024-01-16 14:13:25.813560,1,0,0,0,1,0,0,0,5,2,1

리뷰테이블에 잘 들어가 있음

order도 잘 삭제됨~~

 

2. 글 상세페이지, 작성페이지 로그인값 받아서 연결!

→ 모종의 이유로 로그인 페이지에서 로그인 성공하고 헤더에 달아둔게 페이지 이동하면 달리지 않아 있어서 인증 실패하는 이슈로 그냥 페이지만 다듬고 말았다 ㅋㅋ

 

일단 infowindow사이즈를 줄여봄

infoWindow.setContent([
        '<div style="padding:10px;width:380px;font-size:14px;line-height:20px;">',
        '<div> ' + ' ' + '</div>',
        '</div>'
      ].join(''));
      infoWindow.open(map, latlng);

infoWindow.setContent([
        '<div style="padding:10px;width:10px;line-height:20px;">',
        '<div> ' + ' ' + '</div>',
        '</div>'
      ].join(''));
      infoWindow.open(map, latlng);

그리고 카테고리 선택 dropdown을 달아봄

원래 있던 input form 도 부트스트랩으로 좀 보기 좋게 바꿔봄

가운데 정렬 해봄

원래 코드

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
        name="viewport">
  <title>글 작성하기</title>
  <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
  <script src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=sa2ld1fdvv&submodules=geocoder"
          type="text/javascript"></script>
</head>
<body>
<div class="card-body">
  <input class="form-control" id="store" placeholder="가게" type="text">
  <input class="form-control" id="minPrice" placeholder="최소주문금액" type="text">
  <input class="form-control" id="deliveryCost" placeholder="배달비" type="text">
  <input class="form-control" id="deadlineMins" placeholder="마감시간 시간" type="text">
  <input class="form-control" id="deadlineHours" placeholder="마감시간 분" type="text">
</div>
<button class="btn btn-outline-secondary" onclick="sendData()" type="button">작성 완료</button>

<div id="map" style="width:100%; height:800px;"></div>

<script th:inline="javascript">
  var map = new naver.maps.Map("map", {
        center: new naver.maps.LatLng(37.5666103, 126.9783882),
        zoom: 16
      }),
      infoWindow = null;

  var latlng;

  function initGeocoder() {
    latlng = map.getCenter();

    infoWindow = new naver.maps.InfoWindow({
      content: ''
    });

    map.addListener('click', function (e) {
      latlng = e.coord;

      infoWindow.setContent([
        '<div style="padding:10px;width:10px;line-height:20px;">',
        '<div> ' + ' ' + '</div>',
        '</div>'
      ].join(''));
      infoWindow.open(map, latlng);
    });
  }

  naver.maps.onJSContentLoaded = initGeocoder;

  function sendData() {

    let address = latlng.toString();
    let store = $('#store').val();
    let minPrice = $('#minPrice').val();
    let deliveryCost = $('#deliveryCost').val();
    let deadlineMins = $('#deadlineMins').val();
    let deadlineHours = $('#deadlineHours').val();
    $.ajax({
      type: 'POST',
      url: "/api/v1/test/posts",
      dataType: "json",
      contentType: 'application/json',
      data: JSON.stringify({
        address: address,
        store: store,
        minPrice: minPrice,
        deliveryCost: deliveryCost,
        deadlineMins: deadlineMins,
        deadlineHours: deadlineHours
      }),
      success: function (response) {
        console.log('Success:', response);
      },
      error: function (error) {
        console.error('Error:', error);
      }
    });
  }
</script>

</body>
</html>

input form 바꾸는중

바꿔도 뭔가 구제불능인데?

부트스트랩대로 안 불러와지는 거 같음

<!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
  <link href="./css/sticky-footer-navbar.css" rel="stylesheet">
  <link rel="stylesheet" href="./css/style.css">

이걸 head에 추가해봄!

와 대폭 향상됨!!

<div style="padding:10px;width:1000px; height:max-content; margin:auto">

가운데정렬도 했더니 나름 멀쩡해졌다!

  	@NotBlank String address, 
    @NotBlank String store, 
    @NotNull Integer minPrice,
    @NotNull Integer deliveryCost,

    @NotNull @Max(59) Integer deadlineMins, 
    @NotNull @Max(3) Integer deadlineHours,
    CategoryEnum category
   	ALL,
    BURGER,
    CHICKEN,
    PIZZA,
    KOREAN,
    SNACK,
    WESTERN,
    ASIAN,
    JAPANESE,
    CHINESE,
    LUNCHBOX;
<div class="dropdown">
      <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
        카테고리
      </button>
      <ul class="dropdown-menu">
        <li><a class="dropdown-item" href="#">기타</a></li>
        <li><a class="dropdown-item" href="#">버거</a></li>
        <li><a class="dropdown-item" href="#">치킨</a></li>
        <li><a class="dropdown-item" href="#">한식</a></li>
        <li><a class="dropdown-item" href="#">양식</a></li>
        <li><a class="dropdown-item" href="#">아시안</a></li>
        <li><a class="dropdown-item" href="#">분식</a></li>
        <li><a class="dropdown-item" href="#">일식</a></li>
        <li><a class="dropdown-item" href="#">중식</a></li>
        <li><a class="dropdown-item" href="#">도시락</a></li>
      </ul>
    </div>

단순노동의 현장⛏️

 

카테고리 드롭다운이 안 됨

포기하고 일반 버튼 11개 넣음

https://getbootstrap.com/docs/5.3/components/button-group/#checkbox-and-radio-button-groups

radio button 11개 넣음!

<div>카테고리 선택</div>
    <div class="btn-group" role="group" aria-label="Basic radio toggle button group">
      <input type="radio" class="btn-check" name="btnradio" onclick="etc()" id="else" autocomplete="off" checked>
      <label class="btn btn-outline-secondary" for="else">기타</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="burger()" id="burger" autocomplete="off">
      <label class="btn btn-outline-secondary" for="burger">버거</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="chicken()" id="chicken" autocomplete="off">
      <label class="btn btn-outline-secondary" for="chicken">치킨</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="korean()" id="korean" autocomplete="off">
      <label class="btn btn-outline-secondary" for="korean">한식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="western()" id="western" autocomplete="off">
      <label class="btn btn-outline-secondary" for="western">양식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="asian()" id="asian" autocomplete="off">
      <label class="btn btn-outline-secondary" for="asian">아시안</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="snack()" id="snack" autocomplete="off">
      <label class="btn btn-outline-secondary" for="snack">분식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="japanese()" id="japanese" autocomplete="off">
      <label class="btn btn-outline-secondary" for="japanese">일식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="chinese()" id="chinese" autocomplete="off">
      <label class="btn btn-outline-secondary" for="chinese">중식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="lunchbox()" id="lunchbox" autocomplete="off">
      <label class="btn btn-outline-secondary" for="lunchbox">도시락</label>
    </div>

단순노동의 현장

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"
      name="viewport">
  <!-- Bootstrap CSS -->
  <link crossorigin="anonymous"
        href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
        integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
        rel="stylesheet">
  <link href="./css/sticky-footer-navbar.css" rel="stylesheet">
  <link href="./css/style.css" rel="stylesheet">
  <title>글 작성하기</title>
  <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
  <script
      src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpClientId=sa2ld1fdvv&submodules=geocoder"
      type="text/javascript"></script>
</head>
<body>

<div style="padding:10px;width:1000px; height:max-content; margin:auto">
  <h1>글 작성하기</h1>
  <div class="card-body">
    <div class="input-group mb-3">
      <span class="input-group-text">가게</span>
      <input aria-describedby="inputGroup-sizing-default" aria-label="Sizing example input"
             class="form-control" id="store"
             type="text">
    </div>
    <div class="input-group mb-3">
      <span class="input-group-text">최소주문금액</span>
      <input aria-describedby="inputGroup-sizing-default" aria-label="Sizing example input"
             class="form-control" id="minPrice"
             type="text">
    </div>
    <div class="input-group mb-3">
      <span class="input-group-text">배달비</span>
      <input aria-describedby="inputGroup-sizing-default" aria-label="Sizing example input"
             class="form-control" id="deliveryCost"
             type="text">
    </div>
    <div class="input-group">
      <span class="input-group-text">마감시간</span>
      <input aria-label="시간" placeholder="시간" class="form-control" id="deadlineHours"
             type="text">
      <input aria-label="분" placeholder="분" class="form-control" id="deadlineMins" type="text">
    </div>
    <div>카테고리 선택</div>
    <div class="btn-group" role="group" aria-label="Basic radio toggle button group">
      <input type="radio" class="btn-check" name="btnradio" onclick="etc()" id="else" autocomplete="off" checked>
      <label class="btn btn-outline-secondary" for="else">기타</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="burger()" id="burger" autocomplete="off">
      <label class="btn btn-outline-secondary" for="burger">버거</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="chicken()" id="chicken" autocomplete="off">
      <label class="btn btn-outline-secondary" for="chicken">치킨</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="korean()" id="korean" autocomplete="off">
      <label class="btn btn-outline-secondary" for="korean">한식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="western()" id="western" autocomplete="off">
      <label class="btn btn-outline-secondary" for="western">양식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="asian()" id="asian" autocomplete="off">
      <label class="btn btn-outline-secondary" for="asian">아시안</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="snack()" id="snack" autocomplete="off">
      <label class="btn btn-outline-secondary" for="snack">분식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="japanese()" id="japanese" autocomplete="off">
      <label class="btn btn-outline-secondary" for="japanese">일식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="chinese()" id="chinese" autocomplete="off">
      <label class="btn btn-outline-secondary" for="chinese">중식</label>
      <input type="radio" class="btn-check" name="btnradio" onclick="lunchbox()" id="lunchbox" autocomplete="off">
      <label class="btn btn-outline-secondary" for="lunchbox">도시락</label>
    </div>

  </div>

  <div style="padding:10px;width:1000px; height:max-content; margin:auto">
    <div>지도를 클릭해서 장소를 지정해 주세요!</div>
    <div id="map" padding = "10px"; style="width:1000px; height:450px;"></div>
    <button class="btn btn-outline-secondary" onclick="sendData()" type="button">작성 완료</button>
  </div>

  </div>
</div>


<script th:inline="javascript">
  var map = new naver.maps.Map("map", {
        center: new naver.maps.LatLng(37.5666103, 126.9783882),
        zoom: 16
      }),
      infoWindow = null;

  var latlng;
  var category;

  function initGeocoder() {
    latlng = map.getCenter();

    infoWindow = new naver.maps.InfoWindow({
      content: ''
    });

    map.addListener('click', function (e) {
      latlng = e.coord;

      infoWindow.setContent([
        '<div style="padding:10px;width:10px;line-height:20px;">',
        '<div> ' + ' ' + '</div>',
        '</div>'
      ].join(''));
      infoWindow.open(map, latlng);
    });
  }

  naver.maps.onJSContentLoaded = initGeocoder;

  function sendData() {

    let address = latlng.toString();
    let store = $('#store').val();
    let minPrice = $('#minPrice').val();
    let deliveryCost = $('#deliveryCost').val();
    let deadlineMins = $('#deadlineMins').val();
    let deadlineHours = $('#deadlineHours').val();
    $.ajax({
      type: 'POST',
      url: "/api/v1/test/posts",
      dataType: "json",
      contentType: 'application/json',
      data: JSON.stringify({
        address: address,
        store: store,
        minPrice: minPrice,
        deliveryCost: deliveryCost,
        deadlineMins: deadlineMins,
        deadlineHours: deadlineHours,
        category: category
      }),
      success: function (response) {
        console.log('Success:', response);
      },
      error: function (error) {
        console.error('Error:', error);
      }
    });
  }

  function etc(){
    category = null;
  }
  function burger(){
    category = BURGER;
  }
  function chicken(){
    category = CHICKEN;
  }
  function korean(){
    category = KOREAN;
  }
  function western(){
    category = WESTERN;
  }
  function asian(){
    category = ASIAN;
  }
  function snack(){
    category = SNACK;
  }
  function japanese(){
    category = JAPANESE;
  }
  function chinese(){
    category = CHINESE;
  }
  function lunchbox(){
    category = LUNCHBOX;
  }
</script>

</body>
</html>

단순노동 해봤는데 카테고리가 안 넘어감!

@NotBlank String address,
    @NotBlank String store,
    @NotNull Integer minPrice,
    @NotNull Integer deliveryCost,

    @NotNull @Max(59) Integer deadlineMins,
    @NotNull @Max(3) Integer deadlineHours,
    CategoryEnum category

왜 안 넘어가니

전역변수도 ajax 안에서 안 읽어지는거같음

let으로 위에다 선언을 한번 해봄

let address = latlng.toString();
    let store = $('#store').val();
    let minPrice = $('#minPrice').val();
    let deliveryCost = $('#deliveryCost').val();
    let deadlineMins = $('#deadlineMins').val();
    let deadlineHours = $('#deadlineHours').val();
    let category = categoryEnum;
    $.ajax({
      type: 'POST',
      url: "/api/v1/test/posts",
      dataType: "json",
      contentType: 'application/json',
      data: JSON.stringify({
        address: address,
        store: store,
        minPrice: minPrice,
        deliveryCost: deliveryCost,
        deadlineMins: deadlineMins,
        deadlineHours: deadlineHours,
        category: category

 

String으로 바꿔서 넣어봄

function chicken(){
    categoryEnum = CHICKEN;
  }

function chicken(){
    categoryEnum = "CHICKEN";
  }

ㅋㅋㅋㅋ

해결됨!

나는 무슨 생각으로 저걸 String값을 안 넣고 변수처럼 넣고 앉아있었을까…

 

이제 로그인 후 로그인 정보 이용하게 주소 바꿈!

//set deadline to hours and mins after now
        LocalDateTime deadline = LocalDateTime.now().plusMinutes(postReq.deadlineMins())
            .plusHours(postReq.deadlineHours());

        //get latitude and longitude from the coordinate
        String address = postReq.address();
        address = address.replace("(lat:", "");
        address = address.replace("lng:", "");
        address = address.replace(")", "");
        String[] location = address.split(",");
        double latitude = Double.valueOf(location[0]);
        double longitude = Double.valueOf(location[1]);

        //Build new post with the post request dto
        Post post = Post.builder()
            .address(address)
            .latitude(latitude)
            .longitude(longitude)
            .store(postReq.store())
            .deliveryCost(postReq.deliveryCost())
            .minPrice(postReq.minPrice())
            .deadline(deadline)
            .category(postReq.category())
            .postStatus(PostStatusEnum.OPEN)
            .build();

        //save the post
        postRepository.save(post);

        //Build new relation between the post and the user
        UserPost userpost = UserPost.builder()
            .user(user)
            .post(post)
            .role(UserPostRole.HOST)
            .build();

        //save the relation
        userPostRepository.save(userpost);

로그인 페이지에서 로그인 되었다고 해서 신나게 글 페이지로 가서 작성해봤는데 안 됨?

로그인 정보 거른 path로 보내면 되고

로그인 정보 안 거른 path로 보내면 안 됨

우리가 만든 로그인 창에서 헤더에다 달아준 authorization이 다른 페이지로 넘어가면 없어짐

그래서 오늘 저건 저기까지 하고 놔둠

 

🚀issue: 

리뷰 엔티티 마음이 급해서 그냥 우리 ERD에 있는대로 작성해놨는데

팀원이 저걸 Map에다 넣다가 하드코딩이라서 맘에 안들고 고민된다고 함

근데 사실 나도 단순노동⛏️ ifelse로 짜다가 저게 맞나...싶었음

내가봐도 이상했음

내가 애초에 저따위로 해둔게 문제였다 ㅜㅜ

더 생각하고 바꿔가면서 짤걸….

프로젝트 초반에 ERD 설계할때 저렇게하면 리뷰 테이블 조회를 조금만 하지 않을까? 이렇게 생각하고 저렇게 했던거 같은데 완전 이상한 설계였다는걸 알게됨...! 다시는 저렇게 안 짤듯함

아무튼 구조를 변경해봄!

 

원래 코드!

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "tb_review")
public class Review extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @Column
    private Integer score;

    @Column
    private Integer count;

    @Column
    private Integer goodmanner;

    @Column
    private Integer goodtime;

    @Column
    private Integer goodcomm;

    @Column
    private Integer badtime;

    @Column
    private Integer noshow;

    @Column
    private Integer nomoney;

    @Column
    private Integer badcomm;

    @Column
    private Integer badmanner;

    public Review(User user) {
        this.user = user;
        this.score = 0;
        this.count = 0;
        this.goodmanner = 0;
        this.goodtime = 0;
        this.goodcomm = 0;
        this.badtime = 0;
        this.noshow = 0;
        this.nomoney = 0;
        this.badcomm = 0;
        this.badmanner = 0;
    }

    public void increaseScoreAndCount(ScoreEnum scoreEnum) {
        this.score += scoreEnum.getScore();
        this.count += 1;
    }

    public void increaseGoodmanner() {
        this.goodmanner++;
    }

    public void increaseGoodtime() {
        this.goodtime++;
    }

    public void increaseGoodcomm() {
        this.goodcomm++;
    }

    public void increaseBadtime() {
        this.badtime++;
    }

    public void increaseNoshow() {
        this.noshow++;
    }

    public void increaseNomoney() {
        this.nomoney++;
    }

    public void increaseBadcomm() {
        this.badcomm++;
    }

    public void increaseBadmanner() {
        this.badmanner++;
    }

리팩토링 진행함

@Column
    private Map<String,Integer> reviews;

이렇게 해보니까 빨간줄이 뜸

@Getter
@RequiredArgsConstructor
public class ReviewElement {
    
    private final ReviewEnum review;
    private Integer count;
    
}
@Column
    private List<ReviewElement> reviews;

원래 Map으로 하려했는데

'Basic' attribute type should not be a map

계속 이거 떠서

그냥 객체 파서 List로 진행함

이렇게 넣었더니 또

'Basic' attribute type should not be a container

@Column
    private List<ReviewElement> reviews;

 

근데 내가 생각해도 RDBMS가 객체형태나 List 자체를 저장할 수 있을거 같지가 않아서 그냥 Score랑 Review랑 하나씩만 가지게 다 분리해버림

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "tb_score")
public class Score {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @Column
    private Integer total;

    @Column
    private Integer count;
    
    public Score(User user){
        this.user = user;
        total = 0;
        count = 0;
    }

}
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "tb_review")
public class Review extends BaseTime {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @Column
    @Enumerated(EnumType.STRING)
    private ReviewEnum review;

    @Column
    private Integer count;

    @Builder
    public Review(User user,ReviewEnum review) {
        this.user = user;
        this.review = review;
        this.count = 0;
    }

}

→ 얘는 BaseTime도 필요없을 거 같아서 다시 뺌

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "tb_review")
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @JoinColumn(name = "user_id", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY)
    private User user;

    @Column
    @Enumerated(EnumType.STRING)
    private ReviewEnum content;

    @Column
    private Integer count;

    @Builder
    public Review(User user, ReviewEnum content) {
        this.user = user;
        this.content = content;
        this.count = 0;
    }

}

Service단도 리팩토링 진행함

@Override
    public void review(ReviewRequest reviewReq, User user) {
        Order order = findOrderById(reviewReq.orderId());
        //check if the receiver exists, to clarify that the review hasn't been made, not because the user doesn't exist
        if(!order.getUser().getId().equals(user.getId())){
            throw new GlobalException(OrderErrorCode.FORBIDDEN_ACCESS);
        }
        User receiver = order.getReceiver();
        checkIfUserExists(receiver.getId());
        Review review = findReviewByUser(receiver);
        updateReview(review, reviewReq);
        reviewRepository.save(review);
        orderRepository.delete(order);
    }

    private Order findOrderById(Long orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new GlobalException(OrderErrorCode.ORDER_NOT_FOUND));
    }

    private void checkIfUserExists(Long userId) {
        if (!userRepository.existsById(userId)) {
            throw new GlobalException(UserErrorCode.NOT_EXIST_USER);
        }
    }

    private Review findReviewByUser(User user) {
        //get Review when exists, make a new one when not
        return reviewRepository.findByUser(user).orElse(makeReview(user));
    }

    private Review makeReview(User user) {
        return new Review(user);
    }

    private void updateReview(Review review, ReviewRequest reviewReq) {
        review.increaseScoreAndCount(reviewReq.score());

        if (reviewReq.goodmanner()) {
            review.increaseGoodmanner();
        }
        if (reviewReq.goodcomm()) {
            review.increaseGoodcomm();
        }
        if (reviewReq.goodtime()) {
            review.increaseGoodtime();
        }
        if (reviewReq.badtime()) {
            review.increaseBadtime();
        }
        if (reviewReq.noshow()) {
            review.increaseNoshow();
        }
        if (reviewReq.nomoney()) {
            review.increaseNomoney();
        }
        if (reviewReq.badcomm()) {
            review.increaseBadcomm();
        }
        if (reviewReq.badmanner()) {
            review.increaseBadmanner();
        }
    }
private Review findReviewByUserAndContent(User user, ReviewEnum content) {
        //get Review when exists, make a new one when not
        return reviewRepository.findByUserAndContentEquals(user,content).orElse(makeReview(user,content));
    }

    private Review makeReview(User user, ReviewEnum content) {
        return new Review(user,content);
    }
private void updateReview(ReviewRequest reviewReq, User receiver) {

        if (reviewReq.goodmanner()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.goodcomm()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.goodtime()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.badtime()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.noshow()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.nomoney()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.badcomm()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
        if (reviewReq.badmanner()) {
            Review review = findReviewByUserAndContent(receiver,ReviewEnum.GOODMANNER);
            review.increaseCount();
            reviewRepository.save(review);
        }
    }

이렇게 짜다가 ReviewRepository 너무 여러번 조회하길래 그냥 한번만 조회하게 바꾸고 그 Review들의 List를 이용하게 함

  	private Review findReviewByContent(List<Review> reviews, ReviewEnum content, User user) {
        //get Review when exists, make a new one when not
        for(Review review : reviews){
            if(review.getContent().equals(content)){
                reviews.remove(review);
                return review;
            }
        }
        return new Review(user,content);
    }

    private void updateReview(ReviewRequest reviewReq, User receiver) {

        List<Review> reviews = reviewRepository.findAllByUser(receiver);

save도 다 하지 말고 saveAll을 하는게 낫겠음

완성된 코드

@Override
    public void review(ReviewRequest reviewReq, User user) {
        Order order = findOrderById(reviewReq.orderId());
        //check if the receiver exists, to clarify that the review hasn't been made, not because the user doesn't exist
        if(!order.getUser().getId().equals(user.getId())){
            throw new GlobalException(OrderErrorCode.FORBIDDEN_ACCESS);
        }
        User receiver = order.getReceiver();
        checkIfUserExists(receiver.getId());
        updateScore(reviewReq.score(),receiver);
        updateReview(reviewReq, receiver);
        orderRepository.delete(order);
    }

    private Order findOrderById(Long orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new GlobalException(OrderErrorCode.ORDER_NOT_FOUND));
    }

    private void checkIfUserExists(Long userId) {
        if (!userRepository.existsById(userId)) {
            throw new GlobalException(UserErrorCode.NOT_EXIST_USER);
        }
    }

    private void updateScore(ScoreEnum scoreEnum, User user){
        Score score = findScoreByUser(user);
        score.update(scoreEnum);
        scoreRepository.save(score);
    }

    private Score findScoreByUser(User user){
        return scoreRepository.findByUser(user).orElse(new Score(user));
    }

    private Review findReviewByContent(List<Review> reviews, ReviewEnum content, User user) {
        //get Review when exists, make a new one when not
        for(Review review : reviews){
            if(review.getContent().equals(content)){
                reviews.remove(review);
                return review;
            }
        }
        return new Review(user,content);
    }

    private void updateReview(ReviewRequest reviewReq, User receiver) {

        List<Review> reviews = reviewRepository.findAllByUser(receiver);
        List<Review> updated = new ArrayList<>();

        if (reviewReq.goodmanner()) {
            Review review = findReviewByContent(reviews,ReviewEnum.GOODMANNER,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.goodcomm()) {
            Review review = findReviewByContent(reviews,ReviewEnum.GOODCOMM,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.goodtime()) {
            Review review = findReviewByContent(reviews,ReviewEnum.GOODTIME,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.badtime()) {
            Review review = findReviewByContent(reviews,ReviewEnum.BADTIME,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.noshow()) {
            Review review = findReviewByContent(reviews,ReviewEnum.NOSHOW,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.nomoney()) {
            Review review = findReviewByContent(reviews,ReviewEnum.NOMONEY,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.badcomm()) {
            Review review = findReviewByContent(reviews,ReviewEnum.BADCOMM,receiver);
            review.increaseCount();
            updated.add(review);
        }
        if (reviewReq.badmanner()) {
            Review review = findReviewByContent(reviews,ReviewEnum.BADMANNER,receiver);
            review.increaseCount();
            updated.add(review);
        }
        reviewRepository.saveAll(updated);
    }

 

저 ifelse부분은 dto 구조를 개선하지 않는 한 못 없애겠더라

public record ReviewRequest(
    Long orderId,
    ScoreEnum score,
    boolean goodmanner,
    boolean goodtime,
    boolean goodcomm,
    boolean badtime,
    boolean noshow,
    boolean nomoney,
    boolean badcomm,
    boolean badmanner
) {

}

 

 

테스트해봄

{
    "orderId" : 1,
    "score" : "FIVE",
    "goodmanner" : true
}
{
    "status": 200,
    "message": "리뷰를 성공적으로 전송했습니다.",
    "data": null
}

똑같은 메뉴 두번 날려봤는데 잘 들어감

1,GOODMANNER,2,1

!!!

 

ERD 설계할때 앞으로는 절대 저런식으로 안 해야지….

 

4. 모집마감시 주문최소금액 넘었는지 확인하는 로직 추가

4-1. getSumPrice 리팩토링

모집마감시 sumPrice가 minPrice 넘었는지 체크하는거 하다가

메서드 리팩토링함

private int getSumPrice(List<UserPost> userposts, Post post) {
        int sumPrice = 0;

        //add all prices from the menus from the users who are participating/hosting a post
        for (UserPost userpost : userposts) {
            List<Menu> menus = getUserMenus(userpost.getUser(), post);
            for (Menu menu : menus) {
                sumPrice += menu.getPrice();
            }
        }

        return sumPrice;
    }

왜 menu 테이블을 계속 조회하시는 건가요….

한번에 조회 안되나 싶었음

아 안되는게 맞네

post에 딸린거 다 들고오면 안되네

그래서 그냥 그렇게 함

근데 저렇게 짜놓고 보니 바로 아래에서 menuRepository를 또 조회하고 있었음

//check if sumPrice>minPrice
        if (getSumPrice(userPosts,post)< post.getMinPrice()) {
            throw new GlobalException(PostErrorCode.LOWER_THAN_MINIMUM_PRICE);
        }

        //delete all menus which are not made by participants
        List<Menu> menus = menuRepository.findAllByPost(post);
        for (Menu menu : menus) {
            boolean hasRelation = false;
            for (UserPost userPost : userPosts) {
                if (userPost.getUser().getId().equals(menu.getUser().getId())) {
                    hasRelation = true;
                    break;
                }
            }
            if (!hasRelation) {
                menuRepository.delete(menu);
            }
        }

그래서 menuRepository에서 post로 조회해온 menus를 userpost로 걸러내게 로직 바꿔봄

db 조회를 최소화한다

private int getSumPrice(List<UserPost> userposts, Post post) {
        int sumPrice = 0;

        //add all prices from the menus from the users who are participating/hosting a post
        for (UserPost userpost : userposts) {
            List<Menu> menus = getUserMenus(userpost.getUser(), post);
            for (Menu menu : menus) {
                sumPrice += menu.getPrice();
            }
        }

        return sumPrice;
    }

private int getSumPrice(List<UserPost> userposts, List<Menu> menus) {
        int sumPrice = 0;
        
        //add all prices from the menus from the users who are participating/hosting a post
        for(Menu menu: menus){
            for(UserPost userpost : userposts){
                if(userpost.getUser().getId().equals(menu.getUser().getId())){
                    sumPrice += menu.getPrice();
                    break;
                }
            }
        }
        
        return sumPrice;
    }
private int getSumPrice(List<UserPost> userposts, Post post) {
        List<Menu> menus = menuRepository.findAllByPost(post);
            
        int sumPrice = 0;

        //add all prices from the menus from the users who are participating/hosting a post
        for(Menu menu: menus){
            for(UserPost userpost : userposts){
                if(userpost.getUser().getId().equals(menu.getUser().getId())){
                    sumPrice += menu.getPrice();
                    break;
                }
            }
        }

        return sumPrice;
    }

두개 오버로딩 해둘까 아니면 하나만 쓸까 고민해봄

오버로딩을 이런식으로 해봄

private int getSumPrice(List<UserPost> userposts, Post post){
        List<Menu> menus = menuRepository.findAllByPost(post);
        return getSumPrice(userposts, menus);
    }

    private int getSumPrice(List<UserPost> userposts, List<Menu> menus) {
        int sumPrice = 0;

        //add all prices from the menus from the users who are participating/hosting a post
        for(Menu menu: menus){
            for(UserPost userpost : userposts){
                if(userpost.getUser().getId().equals(menu.getUser().getId())){
                    sumPrice += menu.getPrice();
                    break;
                }
            }
        }

        return sumPrice;
    }

테스트해봄

{
    "status": 400,
    "message": "최소주문금액보다 적으면 모집마감을 할 수 없습니다.",
    "data": null
}

잘 됨!

 

 

'Developing > 개발일지' 카테고리의 다른 글

2024-01-18, Today I Learned  (0) 2024.01.18
2024-01-17, Today I Learned  (0) 2024.01.17
2024-01-15, Today I Learned  (0) 2024.01.15
2024-01-14, Today I Learned  (0) 2024.01.15
2024-01-13, Today I Learned  (0) 2024.01.14