ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,80 @@
+package christmas.io;
+
+import christmas.domain.benefit.Benefits;
+import christmas.domain.Calender;
+import christmas.domain.Order;
+import christmas.domain.OrderItem;
+import java.text.DecimalFormat;
+import java.time.LocalDate;
+import java.util.List;
+
+public class OutputView {
+
+    public static void printWelcomeMessage() {
+        System.out.println("안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.");
+    }
+
+    public static void printEventMessage(Calender calender) {
+        LocalDate date = calender.getDate();
+        System.out.println(date.getMonth().getValue() + "월 " + date.getDayOfMonth() + "일에 우테코 식당에서 받을 이벤트 혜택 미리 보기! \n");
+    }
+
+    public static void printOrderMenu(Order order) {
+        System.out.println("<주문 메뉴>");
+        List<OrderItem> orderItems = order.getOrderItems();
+        orderItems.forEach(
+                orderItem -> System.out.println(orderItem.getName() + " " + orderItem.getOrderCount() + "개")
+        );
+        System.out.println();
+    }
+
+    public static void printTotalPrice(Order order) {
+        int totalPrice = order.calculateTotalPrice();
+        DecimalFormat df = new DecimalFormat("###,###");
+        System.out.println("<할인 전 총주문 금액>");
+        System.out.println(df.format(totalPrice) + "원\n");
+    }
+
+    public static void printBenefit(Order order, Benefits benefits){
+        printGiftBenefit(benefits);
+        printBenefits(benefits);
+        printTotalDiscountPrice(benefits);
+        printAfterDiscountPrice(order, benefits);
+        printBadge(benefits);
+    }
+
+    public static void printGiftBenefit(Benefits benefits) {
+        System.out.println("<증정 메뉴>");
+        System.out.println(benefits.getGiftBenefit() + "\n");
+    }
+
+    public static void printBenefits(Benefits benefits) {
+        System.out.println("<혜택 내역>");
+        benefits.getBenefitItems().forEach(
+                benefitItem -> {
+                    DecimalFormat df = new DecimalFormat("###,###");
+                    System.out.println(benefitItem.getName() + ": -" + df.format(benefitItem.getDiscountPrice()) + "원");
+                }
+        );
+        System.out.println();
+    }
+
+    public static void printTotalDiscountPrice(Benefits benefits) {
+        System.out.println("<총혜택 금액>");
+        int totalDiscountPrice = benefits.getBenefitPrice();
+        DecimalFormat df = new DecimalFormat("###,###");
+        System.out.println("-" + df.format(totalDiscountPrice) + "원\n");
+    }
+
+    public static void printAfterDiscountPrice(Order order, Benefits benefits) {
+        int afterDiscountPrice = order.calculateTotalPrice() - benefits.getDiscountPrice();
+        DecimalFormat df = new DecimalFormat("###,###");
+        System.out.println("<할인 후 예상 결제 금액>");
+        System.out.println(df.format(afterDiscountPrice) + "원\n");
+    }
+
+    public static void printBadge(Benefits benefits) {
+        System.out.println("<12월 이벤트 배지>");
+        System.out.println(benefits.getBadges().getName());
+    }
+}

리뷰: DecimalFormat 이라는 것도 있군요...
더 찾아봐야겠어요
@@ -0,0 +1,80 @@
+package christmas.io;
+
+import christmas.domain.benefit.Benefits;
+import christmas.domain.Calender;
+import christmas.domain.Order;
+import christmas.domain.OrderItem;
+import java.text.DecimalFormat;
+import java.time.LocalDate;
+import java.util.List;
+
+public class OutputView {
+
+    public static void printWelcomeMessage() {
+        System.out.println("안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.");
+    }
+
+    public static void printEventMessage(Calender calender) {
+        LocalDate date = calender.getDate();
+        System.out.println(date.getMonth().getValue() + "월 " + date.getDayOfMonth() + "일에 우테코 식당에서 받을 이벤트 혜택 미리 보기! \n");
+    }
+
+    public static void printOrderMenu(Order order) {
+        System.out.println("<주문 메뉴>");
+        List<OrderItem> orderItems = order.getOrderItems();
+        orderItems.forEach(
+                orderItem -> System.out.println(orderItem.getName() + " " + orderItem.getOrderCount() + "개")
+        );
+        System.out.println();
+    }
+
+    public static void printTotalPrice(Order order) {
+        int totalPrice = order.calculateTotalPrice();
+        DecimalFormat df = new DecimalFormat("###,###");
+        System.out.println("<할인 전 총주문 금액>");
+        System.out.println(df.format(totalPrice) + "원\n");
+    }
+
+    public static void printBenefit(Order order, Benefits benefits){
+        printGiftBenefit(benefits);
+        printBenefits(benefits);
+        printTotalDiscountPrice(benefits);
+        printAfterDiscountPrice(order, benefits);
+        printBadge(benefits);
+    }
+
+    public static void printGiftBenefit(Benefits benefits) {
+        System.out.println("<증정 메뉴>");
+        System.out.println(benefits.getGiftBenefit() + "\n");
+    }
+
+    public static void printBenefits(Benefits benefits) {
+        System.out.println("<혜택 내역>");
+        benefits.getBenefitItems().forEach(
+                benefitItem -> {
+                    DecimalFormat df = new DecimalFormat("###,###");
+                    System.out.println(benefitItem.getName() + ": -" + df.format(benefitItem.getDiscountPrice()) + "원");
+                }
+        );
+        System.out.println();
+    }
+
+    public static void printTotalDiscountPrice(Benefits benefits) {
+        System.out.println("<총혜택 금액>");
+        int totalDiscountPrice = benefits.getBenefitPrice();
+        DecimalFormat df = new DecimalFormat("###,###");
+        System.out.println("-" + df.format(totalDiscountPrice) + "원\n");
+    }
+
+    public static void printAfterDiscountPrice(Order order, Benefits benefits) {
+        int afterDiscountPrice = order.calculateTotalPrice() - benefits.getDiscountPrice();
+        DecimalFormat df = new DecimalFormat("###,###");
+        System.out.println("<할인 후 예상 결제 금액>");
+        System.out.println(df.format(afterDiscountPrice) + "원\n");
+    }
+
+    public static void printBadge(Benefits benefits) {
+        System.out.println("<12월 이벤트 배지>");
+        System.out.println(benefits.getBadges().getName());
+    }
+}

리뷰: 총혜택 금액이 0일 경우 -0으로 출력될 수도 있지 않을까요?
@@ -0,0 +1,44 @@
+package christmas.domain;
+
+import christmas.error.ErrorCode;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class Order {
+
+    private final String DELIMITER = ",";
+    private final List<OrderItem> orderItems;
+
+    public Order(String orderInputs) {
+        this.orderItems = createOrderItems(orderInputs);
+    }
+
+    public List<OrderItem> getOrderItems() {
+        return orderItems;
+    }
+
+    public int calculateTotalPrice() {
+        return orderItems.stream()
+                .mapToInt(OrderItem::getPrice)
+                .sum();
+    }
+
+    private List<OrderItem> createOrderItems(String orderInputs) {
+        List<OrderItem> orderItems = Arrays.stream(orderInputs.split(DELIMITER))
+                .map(OrderItem::new)
+                .toList();
+        validateDuplication(orderItems);
+        return orderItems;
+    }
+
+    private static void validateDuplication(List<OrderItem> orderItems) {
+        Set<String> duplicateChecker = orderItems.stream()
+                .map(OrderItem::getName)
+                .collect(Collectors.toSet());
+        if (duplicateChecker.size() != orderItems.size()) {
+            throw new IllegalArgumentException(ErrorCode.INVALID_MENU_FORMAT.getMessage());
+        }
+    }
+}

리뷰: 메뉴는 한 번 최대 20개까지만 주문할 수 있는데 이 부분은 검증하는 곳이 있을까요?
총주문 금액 10000원 이상부터 이벤트가 적용되는 부분, 음료만 주문 시 주문이 불가능한 것을 체크해주는 부분이 있나요?
@@ -0,0 +1,43 @@
+package com.ll.backend.domain.admin.controller;
+
+import com.ll.backend.domain.admin.dto.AdminRequestDto;
+import com.ll.backend.domain.admin.entity.Admin;
+import com.ll.backend.domain.admin.service.AdminService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/admin")
+public class AdminController {
+    private final AdminService adminService;
+
+    //회원가입
+    @PostMapping("/register")
+    public ResponseEntity<String> registerAdmin(@RequestBody AdminRequestDto request) {
+        try {
+            adminService.registerAdmin(request.getUsername(), request.getPassword());
+            return ResponseEntity.ok("Admin registered successfully.");
+        } catch (Exception e) {
+            return ResponseEntity.badRequest().body("Error: " + e.getMessage());
+        }
+    }
+
+    //로그인
+    @PostMapping("/login")
+    public ResponseEntity<String> loginAdmin(@RequestBody AdminRequestDto request) {
+        try {
+            Admin admin = adminService.validateAdmin(request.getUsername(), request.getPassword());
+            return ResponseEntity.ok("Welcome, " + admin.getUsername());
+        } catch (IllegalArgumentException e) {
+            return ResponseEntity.badRequest().body("Error: " + e.getMessage());
+        }
+    }
+
+
+
+}
+
+
+

리뷰: 외부에는 에러가 최대하게 노출되지 않도록 해야 합니다. (특히나, 에러가 구체화가 되면 안 됩니다.) -> 외부 공격자 입장에서, 이 서버의 취약점이 무엇인지 판단하기 쉬워져요.
@@ -0,0 +1,43 @@
+package com.ll.backend.domain.admin.controller;
+
+import com.ll.backend.domain.admin.dto.AdminRequestDto;
+import com.ll.backend.domain.admin.entity.Admin;
+import com.ll.backend.domain.admin.service.AdminService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/admin")
+public class AdminController {
+    private final AdminService adminService;
+
+    //회원가입
+    @PostMapping("/register")
+    public ResponseEntity<String> registerAdmin(@RequestBody AdminRequestDto request) {
+        try {
+            adminService.registerAdmin(request.getUsername(), request.getPassword());
+            return ResponseEntity.ok("Admin registered successfully.");
+        } catch (Exception e) {
+            return ResponseEntity.badRequest().body("Error: " + e.getMessage());
+        }
+    }
+
+    //로그인
+    @PostMapping("/login")
+    public ResponseEntity<String> loginAdmin(@RequestBody AdminRequestDto request) {
+        try {
+            Admin admin = adminService.validateAdmin(request.getUsername(), request.getPassword());
+            return ResponseEntity.ok("Welcome, " + admin.getUsername());
+        } catch (IllegalArgumentException e) {
+            return ResponseEntity.badRequest().body("Error: " + e.getMessage());
+        }
+    }
+
+
+
+}
+
+
+

리뷰: 만약에 이게 서버 에러가 아니라, 올바르지 않은 값을 필터링하기 위한 예외라면,
`catch (Exception e)` 가 아니라 `catch (ServiceException e)` 와 같이, 여러분들이 만든 예외만 catch  하도록 만들어줘야 합니다.
@@ -0,0 +1,11 @@
+package com.ll.backend.domain.admin.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class AdminRequestDto {
+    private String username;
+    private String password;
+}

리뷰: record로 수정해 주세요.
@@ -0,0 +1,27 @@
+package com.ll.backend.domain.admin.service;
+
+import com.ll.backend.domain.order.entity.Order;
+import com.ll.backend.domain.order.repository.OrderRepository;
+import com.ll.backend.domain.orderdetail.repository.OrderDetailRepository;
+import com.ll.backend.domain.product.repository.ProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class AdminNotificationService {
+    private final OrderRepository orderRepository;
+    private final ProductRepository productRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 결제 완료 시 관리자에게 배송 요청 로직
+    public void notifyAdminForShipping(int orderId) {
+        // 주문 조회
+        Order order = orderRepository.findById(orderId)
+                .orElseThrow(() -> new IllegalArgumentException("Order not found for ID: " + orderId));
+
+        // 관리자에게 알림
+        System.out.println("Admin notified for shipping. Order ID: " + orderId);
+    }
+
+}

리뷰: 가능하면, 이런 식의 "서비스 적인 예외"는 자바에서 정의한 기본 예외를 사용하지 않는 것을 권장드려요.
@@ -0,0 +1,27 @@
+package com.ll.backend.domain.admin.service;
+
+import com.ll.backend.domain.order.entity.Order;
+import com.ll.backend.domain.order.repository.OrderRepository;
+import com.ll.backend.domain.orderdetail.repository.OrderDetailRepository;
+import com.ll.backend.domain.product.repository.ProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class AdminNotificationService {
+    private final OrderRepository orderRepository;
+    private final ProductRepository productRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 결제 완료 시 관리자에게 배송 요청 로직
+    public void notifyAdminForShipping(int orderId) {
+        // 주문 조회
+        Order order = orderRepository.findById(orderId)
+                .orElseThrow(() -> new IllegalArgumentException("Order not found for ID: " + orderId));
+
+        // 관리자에게 알림
+        System.out.println("Admin notified for shipping. Order ID: " + orderId);
+    }
+
+}

리뷰: log로 대체해 주세요.
@@ -0,0 +1,56 @@
+package com.ll.backend.domain.admin.service;
+
+import com.ll.backend.domain.admin.entity.Admin;
+import com.ll.backend.domain.admin.repository.AdminRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+public class AdminService {
+    private final AdminRepository adminRepository;
+    private final PasswordEncoder passwordEncoder;
+
+    //관리자 회원가입
+    public Admin registerAdmin(String username, String password){
+        if (adminRepository.findByUsername(username).isPresent()) {
+            throw new IllegalArgumentException("이미 존재하는 사용자 이름입니다.");
+        }
+
+        Admin admin = new Admin();
+        admin.setUsername(username);
+        admin.setPassword(passwordEncoder.encode(password));
+        return adminRepository.save(admin);
+    }
+
+    //관리자 로그인 검증
+    public Admin validateAdmin(String username, String password){
+        Optional<Admin> oa=adminRepository.findByUsername(username);
+        if(oa.isEmpty()){
+            throw new IllegalArgumentException("사용자를 찾을 수 없습니다.");
+        }
+
+        Admin admin=oa.get();
+        if(!passwordEncoder.matches(password,admin.getPassword())){
+            throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
+        }
+
+        return admin;
+    }
+
+    //관리자 조회
+    public Optional<Admin> getAdmin(String username){
+        return adminRepository.findByUsername(username);
+    }
+
+    public Optional<Admin> findByUsername(String user1) {
+        return adminRepository.findByUsername(user1);
+    }
+
+    public long count() {
+        return adminRepository.count();
+    }
+}

리뷰: 가능하면, 최대한 많은 객체들에서 setter 직접 호출을 배제해 주세요.
@@ -0,0 +1,56 @@
+package com.ll.backend.domain.admin.service;
+
+import com.ll.backend.domain.admin.entity.Admin;
+import com.ll.backend.domain.admin.repository.AdminRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+@RequiredArgsConstructor
+public class AdminService {
+    private final AdminRepository adminRepository;
+    private final PasswordEncoder passwordEncoder;
+
+    //관리자 회원가입
+    public Admin registerAdmin(String username, String password){
+        if (adminRepository.findByUsername(username).isPresent()) {
+            throw new IllegalArgumentException("이미 존재하는 사용자 이름입니다.");
+        }
+
+        Admin admin = new Admin();
+        admin.setUsername(username);
+        admin.setPassword(passwordEncoder.encode(password));
+        return adminRepository.save(admin);
+    }
+
+    //관리자 로그인 검증
+    public Admin validateAdmin(String username, String password){
+        Optional<Admin> oa=adminRepository.findByUsername(username);
+        if(oa.isEmpty()){
+            throw new IllegalArgumentException("사용자를 찾을 수 없습니다.");
+        }
+
+        Admin admin=oa.get();
+        if(!passwordEncoder.matches(password,admin.getPassword())){
+            throw new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
+        }
+
+        return admin;
+    }
+
+    //관리자 조회
+    public Optional<Admin> getAdmin(String username){
+        return adminRepository.findByUsername(username);
+    }
+
+    public Optional<Admin> findByUsername(String user1) {
+        return adminRepository.findByUsername(user1);
+    }
+
+    public long count() {
+        return adminRepository.count();
+    }
+}

리뷰: ```java
		Admin admin = adminRepository.findByUsername(username)
            .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
```
@@ -0,0 +1,42 @@
+package com.ll.backend.domain.order.controller;
+
+import com.ll.backend.domain.order.dto.OrderRequestDto;
+import com.ll.backend.domain.order.entity.Order;
+import com.ll.backend.domain.order.service.OrderService;
+import com.ll.backend.domain.orderdetail.service.OrderDetailService;
+import com.ll.backend.domain.order.dto.OrderResponseDto;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+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.RestController;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+public class OrderController {
+    private final OrderService orderService;
+    private final OrderDetailService orderDetailService;
+
+    record OrderCreated(Integer id,String message) {}
+
+    //주문생성
+    @PostMapping("/api/main/order")
+    public ResponseEntity<OrderCreated> createOrder(@RequestBody OrderRequestDto orderRequestDto){
+        Order order = orderService.createOrder(orderRequestDto);
+        orderDetailService.createOrderDetails(order, orderRequestDto.getProducts());
+        return ResponseEntity.ok(new OrderCreated(order.getId(),"Order successfully created."));
+    }
+
+    @GetMapping("/admin/orderList")
+    public ResponseEntity<List<OrderResponseDto>> getOrderList() {
+        List<OrderResponseDto> orderList = orderService.getOrderList();
+        if (orderList.isEmpty()) {
+            return ResponseEntity.noContent().build();
+        }
+        return ResponseEntity.ok(orderList);
+    }
+
+}

리뷰: 요거는 값이 없다고 해서 status를 다르게 주는 건 아닌 것 같아요.
(204 -> no content 가 맞긴 한데, 저건 응답이 필요 없는 요청에 대한 응답 코드에요.)
@@ -0,0 +1,15 @@
+package com.ll.backend.domain.order.dto;
+
+import com.ll.backend.domain.orderdetail.dto.OrderDetailRequestDto;
+import lombok.Getter;
+
+import java.util.List;
+
+@Getter
+public class OrderRequestDto {
+    private String email;
+    private String address;
+    private String postalCode;
+    private Integer totalPrice;
+    private List<OrderDetailRequestDto> products;
+}

리뷰: Jakarta Validation 적용
@@ -0,0 +1,81 @@
+package com.ll.backend.domain.order.service;
+
+import com.ll.backend.domain.order.OrderStatus;
+import com.ll.backend.domain.order.dto.OrderRequestDto;
+import com.ll.backend.domain.order.dto.OrderResponseDto;
+import com.ll.backend.domain.order.entity.Order;
+import com.ll.backend.domain.order.repository.OrderRepository;
+import com.ll.backend.domain.orderdetail.service.OrderDetailService;
+import com.ll.backend.domain.product.repository.ProductRepository;
+import jakarta.transaction.Transactional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class OrderService {
+    private final OrderRepository orderRepository;
+    private final ProductRepository productRepository;
+    private final OrderDetailService orderDetailService;
+
+
+    // 주문 생성
+    @Transactional
+    public Order createOrder(OrderRequestDto orderRequestDto) {
+
+        // 주문 정보 생성
+        Order order = new Order(
+                orderRequestDto.getEmail(),
+                orderRequestDto.getAddress(),
+                orderRequestDto.getPostalCode(),
+                setOrderStateBasedOnTime(),
+                orderRequestDto.getTotalPrice()
+        );
+
+        // 주문 저장
+        return orderRepository.save(order);
+    }
+
+    private OrderStatus setOrderStateBasedOnTime() {
+        LocalDateTime now = LocalDateTime.now();
+        LocalTime cutoffTime = LocalTime.of(14, 0); // 오후 2시 기준
+
+        if (now.toLocalTime().isBefore(cutoffTime)) {
+            return OrderStatus.SHIPPED;
+        } else {
+            return OrderStatus.PENDING;
+        }
+    }
+
+    public long count() {
+        return orderRepository.count();
+    }
+
+    public Order initDataOrder(Order order) {
+        return orderRepository.save(order);
+    }
+
+    @Transactional()
+    public List<OrderResponseDto> getOrderList() {
+        List<Order> orders = orderRepository.findAll();
+
+        return orders.stream()
+                .map(order -> new OrderResponseDto(
+                        order.getId(),
+                        order.getEmail(),
+                        order.getAddress(),
+                        order.getPostalCode(),
+                        order.getState().toString(),
+                        order.getTotalPrice(),
+                        order.getOrderDate()
+                ))
+                .collect(Collectors.toList());
+    }
+}
+
+

리뷰: 객체 세팅은 가능하면 Service 이외의 곳으로 유배 보내시죠.

```java
		return orderRepository.findAll().stream()
			.map(OrderConverter::toOrderResponse)
			.toList();
```