Skip to content

7주차 미션 / 서버 3조 김나연 - #17

Open
Nayeon07 wants to merge 1 commit into
Konkuk-KUIT:mainfrom
Nayeon07:Nayeon07
Open

Nayeon07 wants to merge 1 commit into
Konkuk-KUIT:mainfrom
Nayeon07:Nayeon07

Conversation

@Nayeon07

@Nayeon07 Nayeon07 commented May 15, 2026 •

Copy link
Copy Markdown

구현한 API 목록

Method URI 설명
GET /restaurants 음식점 목록 조회
GET /restaurants/{restaurantId} 음식점 단건 조회
POST /addresses 주소 등록
GET /members/{memberId}/addresses 회원 주소 목록 조회
GET /restaurants/{restaurantId}/menus 특정 음식점 메뉴 목록 조회
GET /menus/{menuId} 메뉴 상세 조회(옵션 포함)
POST /orders 주문 생성
GET /members/{memberId}/orders 회원 주문 내역 조회
POST /reviews 리뷰 작성
GET /members/{memberId}/reviews 회원 리뷰 목록 조회

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 주소 관리 기능 추가 - 회원이 주소를 등록하고 조회할 수 있습니다.
    • 음식점 검색 및 조회 기능 추가 - 카테고리별로 음식점을 검색하고 상세 정보를 확인할 수 있습니다.
    • 메뉴 조회 기능 추가 - 음식점별 메뉴와 옵션을 확인할 수 있습니다.
    • 주문 기능 추가 - 음식점에서 메뉴를 선택하여 주문하고 주문 목록을 조회할 수 있습니다.
    • 리뷰 기능 추가 - 주문 완료 후 리뷰를 작성하고 조회할 수 있습니다.
    • API 문서(Swagger UI) 통합 - 개발자를 위한 API 명세서가 제공됩니다.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 15, 2026 •

Copy link
Copy Markdown

개요

이 PR은 Spring Boot 기반의 음식 배달 REST API를 구현합니다. Swagger 설정부터 시작해 음식점, 주소, 메뉴, 주문, 리뷰 도메인에 대한 완전한 CRUD 기능을 추가합니다. 각 도메인마다 엔티티, 레포지토리, 요청/응답 DTO, 서비스, 컨트롤러가 계층적으로 구현되어 있습니다.

변경 사항

음식 배달 REST API 핵심 구현

Layer / File(s) 요약
API 기초 설정 및 에러 핸들링
build.gradle, config/SwaggerConfig.java, exception/errorcode/ErrorStatus.java
SpringDoc OpenAPI 의존성을 추가하고 Swagger 메타데이터(제목, 설명, 버전)를 설정합니다. 음식점, 주소, 메뉴, 주문, 리뷰 관련 에러 상태 코드를 정의합니다.
도메인 엔티티 정의
domain/Restaurant/*, domain/address/*, domain/menu/*, domain/order/*, domain/review/*
Restaurant 엔티티에 카테고리, 좌표, 최소주문금액 필드를 추가합니다. Address, Menu, MenuOption, Order(주문), OrderItem, OrderItemOption, Review 엔티티를 새로 정의하고 각 도메인별 상태 enum(ACTIVE, DELETED 등)을 추가합니다.
데이터 접근 계층
repository/*Repository.java
각 엔티티에 대한 Spring Data JPA 리포지토리를 정의하고, 회원ID, 상태, 카테고리 등을 기준으로 조회하는 파생 쿼리 메서드를 추가합니다.
요청 DTO 정의
dto/request/*CreateReq.java
주소 생성, 주문 생성(메뉴 항목 포함), 리뷰 생성 요청을 위한 DTO를 정의하고 Jakarta Validation 제약(필수, 양수, 범위 등)을 적용합니다.
응답 DTO 정의
dto/response/*Res.java
음식점, 주소, 메뉴(상세포함), 주문, 리뷰 조회 응답 DTO를 정의하고 도메인 객체를 응답 형태로 매핑하는 정적 팩토리 메서드를 제공합니다.
비즈니스 로직 계층
service/*Service.java
주소, 음식점, 메뉴, 주문, 리뷰에 대한 조회/생성 로직을 구현합니다. 회원/음식점/메뉴/주문 존재성 검증, 중복 리뷰 방지, 활성 상태 필터링, 페이징 조회, 주문 총액 계산 등을 포함합니다.
HTTP API 계층
controller/*Controller.java
주소, 음식점, 메뉴, 주문, 리뷰 관련 REST 엔드포인트(POST, GET)를 정의합니다. 요청 검증, 서비스 호출, ApiResponse 래핑, 응답 반환을 구성합니다.
개발 환경 설정
application.yml
MySQL 접속정보를 환경변수 대신 하드코딩 값으로 변경하고 JPA DDL 자동 생성 정책을 create에서 update로 변경합니다.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목이 구체적이지 않고 모호하며, 실제 변경 내용(API 엔드포인트 구현)을 반영하지 않습니다. 제목을 '7주차 미션: 주문/배달 REST API 엔드포인트 구현' 또는 유사하게 변경하여 주요 변경 사항을 명확히 드러내세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/kuit/baemin/domain/order/Orders.java`:
- Around line 41-43: Orders 엔티티의 status 필드가 잘못된 enum 타입(OrderItemStatus)으로 선언되어
중복 또는 부적절하게 보입니다; Orders 클래스의 기존 orderStatus 필드와 ERD를 확인해 어느 상태 필드만 남길지 결정한 뒤
수정하세요: 만약 Orders에 하나의 상태만 필요하면 중복된 status 필드를 제거; 만약 두 필드가 모두 필요하지만 의미가 다르면
status의 타입을 OrderStatus로 변경하거나 필드명을 명확히(예: orderItemStatus → orderItemStatus) 하여
역할을 구분하고 JPA 어노테이션(`@Enumerated`, `@Column`)은 유지하세요 (참조 심볼: Orders, status,
orderStatus, OrderItemStatus, OrderStatus).

In `@src/main/java/com/kuit/baemin/domain/review/Review.java`:
- Around line 37-38: The rating field in the Review entity lacks validation for
allowed values; update the Review class so the Integer rating field is annotated
with Jakarta Validation constraints (e.g., `@Min`(1) and `@Max`(5)) to enforce the
1–5 range, and add the corresponding imports (jakarta.validation.constraints.Min
and jakarta.validation.constraints.Max); keep the existing `@Column`(nullable =
false) intact so the DB constraint remains while validation prevents
out-of-range values at the model level.

In `@src/main/java/com/kuit/baemin/dto/request/AddressCreateReq.java`:
- Around line 13-14: Replace the `@NotNull` on the String field addressType in
AddressCreateReq with `@NotBlank` to reject empty or whitespace-only strings;
update the import to use javax.validation.constraints.NotBlank (or jakarta
equivalent used in the project) and run/adjust any validation tests to ensure
the new constraint is applied on AddressCreateReq.addressType.

In `@src/main/java/com/kuit/baemin/dto/response/AddressRes.java`:
- Line 21: address.getMember().getId() can throw NPE if member is null; update
the AddressRes mapping (where .memberId(address.getMember().getId()) is invoked)
to defensively handle a null member: check address.getMember() for null and
supply a safe value (e.g., null or 0) or use
Optional.ofNullable(address.getMember()).map(Member::getId).orElse(null) so
memberId is not dereferenced when member is absent; adjust the AddressRes
builder/constructor to accept a nullable memberId accordingly.

In `@src/main/java/com/kuit/baemin/dto/response/OrderRes.java`:
- Around line 22-31: In OrderRes.of, avoid chaining
order.getRestaurant().getName() which can NPE if getRestaurant() is null; modify
OrderRes.of to safely obtain the restaurant name (e.g., check if
order.getRestaurant() != null and use a safe fallback like null or empty string)
and set that value on the builder (reference: OrderRes.of,
Orders.getRestaurant(), Orders.getRestaurant().getName(), OrderRes.builder());
alternatively ensure the service layer always loads the restaurant before
calling OrderRes.of.

In `@src/main/java/com/kuit/baemin/dto/response/ReviewRes.java`:
- Around line 20-29: The ReviewRes.from factory uses chained calls
review.getOrder().getId() and review.getRestaurant().getName() which can throw
NPE if associated entities are null; update ReviewRes.from to defensively handle
nulls by checking review.getOrder() and review.getRestaurant() (or their
id/name) before accessing members and provide safe fallbacks (e.g., null or
optional values) so that ReviewRes.builder() receives safe values; reference the
ReviewRes.from method and Review#getOrder, Review#getRestaurant,
Review#getRating, Review#getContent to locate and update the code.

In `@src/main/java/com/kuit/baemin/repository/OrderRepository.java`:
- Line 11: 메서드 findByMemberIdAndStatus에서 잘못된 enum 타입을 사용하고 있으니 파라미터 타입을
OrderItemStatus에서 OrderStatus로 변경하고 관련 import 문도 OrderStatus로 교체하세요; 대상은
OrderRepository의 Page<Orders> findByMemberIdAndStatus(Long memberId, OrderStatus
status, Pageable pageable) 시그니처로 수정하는 것입니다.

In `@src/main/java/com/kuit/baemin/repository/ReviewRepository.java`:
- Line 13: The current ReviewRepository method findByMemberIdAndStatus(Long
memberId, ReviewStatus status) should support pagination to avoid loading all
reviews at once; change its signature to return Page<Review> and add a Pageable
parameter (e.g., Page<Review> findByMemberIdAndStatus(Long memberId,
ReviewStatus status, Pageable pageable)), and add the necessary imports for
org.springframework.data.domain.Page and
org.springframework.data.domain.Pageable so callers can pass page/size/sort.

In `@src/main/java/com/kuit/baemin/service/OrderService.java`:
- Around line 111-154: toOrderRes and calculateTotalPrice are causing duplicate
option queries; fetch OrderItemOptions once and reuse them by either (A)
changing the repository call in toOrderRes to a single eager-load method (e.g.,
replace orderItemRepository.findByOrderIdAndStatus(...) with a new
findByOrderIdAndStatusWithOptions(...) that JOIN FETCHes orderItemOptions) and
then use the loaded options both to build OrderItemRes (OrderItemRes.of) and to
compute price, or (B) compute each item's subtotal while mapping in toOrderRes
(use the options loaded there) and remove the separate option lookups from
calculateTotalPrice (or change calculateTotalPrice to accept the already-loaded
OrderItem list with options); update references to OrderItemOptionRepository
lookups (in toOrderRes and calculateTotalPrice) accordingly so options are
queried only once per order.

In `@src/main/resources/application.yml`:
- Line 10: The default configuration currently sets
spring.jpa.hibernate.ddl-auto: update in application.yml which risks unintended
schema changes in non-local environments; change configuration to remove/update
this default and move environment-specific settings into profile-specific files
(e.g., create application-local.yml with spring.jpa.hibernate.ddl-auto: update
and ensure production/other profiles use spring.jpa.hibernate.ddl-auto: validate
or none) and ensure the active profile selection is used at runtime so the
application does not apply `update` unless the "local" profile is explicitly
active.
- Around line 3-5: Remove the hardcoded DB credentials in application.yml (the
url, username, and password properties) and replace them with environment-backed
placeholders (e.g., use environment variables for DB_URL, DB_USERNAME,
DB_PASSWORD) so no plaintext secrets remain in the repo; update any code/config
that reads these properties to use the new env vars and ensure secrets are
loaded via your platform's secret manager or environment, and immediately rotate
the exposed password.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 992e5da6-ccc0-463c-9783-a782d6354f74

📥 Commits

Reviewing files that changed from the base of the PR and between e559830 and 541b2f5.

📒 Files selected for processing (51)
  • build.gradle
  • src/main/java/com/kuit/baemin/config/SwaggerConfig.java
  • src/main/java/com/kuit/baemin/controller/AddressController.java
  • src/main/java/com/kuit/baemin/controller/MenuController.java
  • src/main/java/com/kuit/baemin/controller/OrderController.java
  • src/main/java/com/kuit/baemin/controller/RestaurantController.java
  • src/main/java/com/kuit/baemin/controller/ReviewController.java
  • src/main/java/com/kuit/baemin/domain/Restaurant/Restaurant.java
  • src/main/java/com/kuit/baemin/domain/address/Address.java
  • src/main/java/com/kuit/baemin/domain/address/AddressStatus.java
  • src/main/java/com/kuit/baemin/domain/menu/Menu.java
  • src/main/java/com/kuit/baemin/domain/menu/MenuOption.java
  • src/main/java/com/kuit/baemin/domain/menu/MenuOptionStatus.java
  • src/main/java/com/kuit/baemin/domain/menu/MenuStatus.java
  • src/main/java/com/kuit/baemin/domain/menu/SelectionType.java
  • src/main/java/com/kuit/baemin/domain/order/OrderItem.java
  • src/main/java/com/kuit/baemin/domain/order/OrderItemOption.java
  • src/main/java/com/kuit/baemin/domain/order/OrderItemOptionStatus.java
  • src/main/java/com/kuit/baemin/domain/order/OrderItemStatus.java
  • src/main/java/com/kuit/baemin/domain/order/OrderStatus.java
  • src/main/java/com/kuit/baemin/domain/order/Orders.java
  • src/main/java/com/kuit/baemin/domain/review/Review.java
  • src/main/java/com/kuit/baemin/domain/review/ReviewStatus.java
  • src/main/java/com/kuit/baemin/dto/request/AddressCreateReq.java
  • src/main/java/com/kuit/baemin/dto/request/OrderCreateReq.java
  • src/main/java/com/kuit/baemin/dto/request/OrderItemCreateReq.java
  • src/main/java/com/kuit/baemin/dto/request/ReviewCreateReq.java
  • src/main/java/com/kuit/baemin/dto/response/AddressRes.java
  • src/main/java/com/kuit/baemin/dto/response/MenuDetailRes.java
  • src/main/java/com/kuit/baemin/dto/response/MenuOptionRes.java
  • src/main/java/com/kuit/baemin/dto/response/MenuRes.java
  • src/main/java/com/kuit/baemin/dto/response/OrderItemOptionRes.java
  • src/main/java/com/kuit/baemin/dto/response/OrderItemRes.java
  • src/main/java/com/kuit/baemin/dto/response/OrderRes.java
  • src/main/java/com/kuit/baemin/dto/response/RestaurantRes.java
  • src/main/java/com/kuit/baemin/dto/response/ReviewRes.java
  • src/main/java/com/kuit/baemin/exception/errorcode/ErrorStatus.java
  • src/main/java/com/kuit/baemin/repository/AddressRepository.java
  • src/main/java/com/kuit/baemin/repository/MenuOptionRepository.java
  • src/main/java/com/kuit/baemin/repository/MenuRepository.java
  • src/main/java/com/kuit/baemin/repository/OrderItemOptionRepository.java
  • src/main/java/com/kuit/baemin/repository/OrderItemRepository.java
  • src/main/java/com/kuit/baemin/repository/OrderRepository.java
  • src/main/java/com/kuit/baemin/repository/RestaurantRepository.java
  • src/main/java/com/kuit/baemin/repository/ReviewRepository.java
  • src/main/java/com/kuit/baemin/service/AddressService.java
  • src/main/java/com/kuit/baemin/service/MenuService.java
  • src/main/java/com/kuit/baemin/service/OrderService.java
  • src/main/java/com/kuit/baemin/service/RestaurantService.java
  • src/main/java/com/kuit/baemin/service/ReviewService.java
  • src/main/resources/application.yml

Comment on lines +41 to +43
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private OrderItemStatus status;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

잘못된 enum 타입 사용.

status 필드가 OrderItemStatus 타입으로 선언되어 있는데, 이는 Orders 엔티티가 아닌 OrderItem에 적합한 상태 타입입니다. Orders 엔티티에는 이미 line 39에서 OrderStatus 타입의 orderStatus 필드가 선언되어 있습니다.

두 필드가 중복되거나, status 필드의 타입이 잘못 지정된 것으로 보입니다. ERD를 확인하여 Orders 테이블에 필요한 상태 필드가 무엇인지 검증해주세요.

🐛 제안하는 수정 방안

만약 하나의 상태 필드만 필요하다면:

 `@Enumerated`(EnumType.STRING)
 `@Column`(nullable = false, length = 20)
 private OrderStatus orderStatus;

-@Enumerated(EnumType.STRING)
-@Column(nullable = false, length = 10)
-private OrderItemStatus status;

또는 필드명을 명확히 구분해야 한다면 타입을 수정:

 `@Enumerated`(EnumType.STRING)
-@Column(nullable = false, length = 10)
-private OrderItemStatus status;
+@Column(nullable = false, length = 20)
+private OrderStatus status;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private OrderItemStatus status;
`@Enumerated`(EnumType.STRING)
`@Column`(nullable = false, length = 20)
private OrderStatus orderStatus;
Suggested change
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private OrderItemStatus status;
`@Enumerated`(EnumType.STRING)
`@Column`(nullable = false, length = 20)
private OrderStatus status;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/domain/order/Orders.java` around lines 41 - 43,
Orders 엔티티의 status 필드가 잘못된 enum 타입(OrderItemStatus)으로 선언되어 중복 또는 부적절하게 보입니다;
Orders 클래스의 기존 orderStatus 필드와 ERD를 확인해 어느 상태 필드만 남길지 결정한 뒤 수정하세요: 만약 Orders에
하나의 상태만 필요하면 중복된 status 필드를 제거; 만약 두 필드가 모두 필요하지만 의미가 다르면 status의 타입을
OrderStatus로 변경하거나 필드명을 명확히(예: orderItemStatus → orderItemStatus) 하여 역할을 구분하고
JPA 어노테이션(`@Enumerated`, `@Column`)은 유지하세요 (참조 심볼: Orders, status, orderStatus,
OrderItemStatus, OrderStatus).

Comment on lines +37 to +38
@Column(nullable = false)
private Integer rating;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

평점 범위 검증 추가 권장.

rating 필드에 범위 제약이 없습니다. 일반적으로 평점은 1-5점 또는 1-10점 범위로 제한되는데, 현재는 음수나 과도하게 큰 값도 허용됩니다.

Jakarta Validation의 @Min, @Max 어노테이션을 사용하여 유효한 범위를 명시하는 것을 권장합니다.

✨ 제안하는 개선 방안 (1~5점 기준)
+import jakarta.validation.constraints.Max;
+import jakarta.validation.constraints.Min;

 `@Column`(nullable = false)
+@Min(1)
+@Max(5)
 private Integer rating;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Column(nullable = false)
private Integer rating;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
`@Column`(nullable = false)
`@Min`(1)
`@Max`(5)
private Integer rating;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/domain/review/Review.java` around lines 37 -
38, The rating field in the Review entity lacks validation for allowed values;
update the Review class so the Integer rating field is annotated with Jakarta
Validation constraints (e.g., `@Min`(1) and `@Max`(5)) to enforce the 1–5 range, and
add the corresponding imports (jakarta.validation.constraints.Min and
jakarta.validation.constraints.Max); keep the existing `@Column`(nullable = false)
intact so the DB constraint remains while validation prevents out-of-range
values at the model level.

Comment on lines +13 to +14
@NotNull(message = "주소 타입은 필수입니다.")
private String addressType;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

String 필드에는 @NotBlank 사용을 권장합니다.

addressType은 String 타입이므로 @NotNull 대신 @NotBlank를 사용하는 것이 좋습니다. @NotNull은 빈 문자열("")과 공백만 있는 문자열을 허용하지만, @NotBlank는 이를 모두 거부합니다.

✅ 수정 제안
-    `@NotNull`(message = "주소 타입은 필수입니다.")
+    `@NotBlank`(message = "주소 타입은 필수입니다.")
     private String addressType;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@NotNull(message = "주소 타입은 필수입니다.")
private String addressType;
`@NotBlank`(message = "주소 타입은 필수입니다.")
private String addressType;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/dto/request/AddressCreateReq.java` around lines
13 - 14, Replace the `@NotNull` on the String field addressType in
AddressCreateReq with `@NotBlank` to reject empty or whitespace-only strings;
update the import to use javax.validation.constraints.NotBlank (or jakarta
equivalent used in the project) and run/adjust any validation tests to ensure
the new constraint is applied on AddressCreateReq.addressType.

public static AddressRes from(Address address) {
return AddressRes.builder()
.id(address.getId())
.memberId(address.getMember().getId())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Null 포인터 예외(NPE) 가능성이 있습니다.

address.getMember().getId() 호출 시 getMember()가 null을 반환하면 NPE가 발생합니다. JPA 연관관계에서 Member가 지연 로딩되거나 null일 수 있습니다.

🛡️ 수정 제안

방법 1: Null 체크 추가

 public static AddressRes from(Address address) {
+    if (address.getMember() == null) {
+        throw new IllegalStateException("Address must have an associated member");
+    }
     return AddressRes.builder()
             .id(address.getId())
             .memberId(address.getMember().getId())

방법 2: Optional 사용

-    private Long memberId;
+    private Long memberId; // null 허용으로 변경하거나
 public static AddressRes from(Address address) {
     return AddressRes.builder()
             .id(address.getId())
-            .memberId(address.getMember().getId())
+            .memberId(address.getMember() != null ? address.getMember().getId() : null)

Address 엔티티의 member 필드가 @ManyToOne(optional=false)로 정의되어 있고 데이터 무결성이 보장된다면, 방법 1을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/dto/response/AddressRes.java` at line 21,
address.getMember().getId() can throw NPE if member is null; update the
AddressRes mapping (where .memberId(address.getMember().getId()) is invoked) to
defensively handle a null member: check address.getMember() for null and supply
a safe value (e.g., null or 0) or use
Optional.ofNullable(address.getMember()).map(Member::getId).orElse(null) so
memberId is not dereferenced when member is absent; adjust the AddressRes
builder/constructor to accept a nullable memberId accordingly.

Comment on lines +22 to +31
public static OrderRes of(Orders order, Integer totalPrice, List<OrderItemRes> items) {
return OrderRes.builder()
.id(order.getId())
.restaurantName(order.getRestaurant().getName())
.orderStatus(order.getOrderStatus())
.totalPrice(totalPrice)
.createdAt(order.getCreatedAt())
.items(items)
.build();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

연관 객체 탐색 시 NPE 위험

Line 25에서 order.getRestaurant().getName()와 같이 체이닝된 호출을 사용하고 있습니다. 만약 getRestaurant()가 null을 반환하면 NullPointerException이 발생할 수 있습니다. 연관 엔티티가 항상 로드된다는 보장이 없다면 null 체크를 추가하는 것이 안전합니다.

🛡️ null 안전성을 위한 수정 제안
 public static OrderRes of(Orders order, Integer totalPrice, List<OrderItemRes> items) {
+    String restaurantName = order.getRestaurant() != null 
+        ? order.getRestaurant().getName() 
+        : null;
+
     return OrderRes.builder()
             .id(order.getId())
-            .restaurantName(order.getRestaurant().getName())
+            .restaurantName(restaurantName)
             .orderStatus(order.getOrderStatus())
             .totalPrice(totalPrice)
             .createdAt(order.getCreatedAt())
             .items(items)
             .build();
 }

또는 서비스 레이어에서 연관 엔티티가 항상 로드되도록 보장하는 방법도 고려해보세요.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static OrderRes of(Orders order, Integer totalPrice, List<OrderItemRes> items) {
return OrderRes.builder()
.id(order.getId())
.restaurantName(order.getRestaurant().getName())
.orderStatus(order.getOrderStatus())
.totalPrice(totalPrice)
.createdAt(order.getCreatedAt())
.items(items)
.build();
}
public static OrderRes of(Orders order, Integer totalPrice, List<OrderItemRes> items) {
String restaurantName = order.getRestaurant() != null
? order.getRestaurant().getName()
: null;
return OrderRes.builder()
.id(order.getId())
.restaurantName(restaurantName)
.orderStatus(order.getOrderStatus())
.totalPrice(totalPrice)
.createdAt(order.getCreatedAt())
.items(items)
.build();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/dto/response/OrderRes.java` around lines 22 -
31, In OrderRes.of, avoid chaining order.getRestaurant().getName() which can NPE
if getRestaurant() is null; modify OrderRes.of to safely obtain the restaurant
name (e.g., check if order.getRestaurant() != null and use a safe fallback like
null or empty string) and set that value on the builder (reference: OrderRes.of,
Orders.getRestaurant(), Orders.getRestaurant().getName(), OrderRes.builder());
alternatively ensure the service layer always loads the restaurant before
calling OrderRes.of.


public interface OrderRepository extends JpaRepository<Orders, Long> {

Page<Orders> findByMemberIdAndStatus(Long memberId, OrderItemStatus status, Pageable pageable);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

잘못된 상태 타입이 사용되었습니다.

Orders 엔티티를 조회하는 메서드에서 OrderItemStatus가 아닌 OrderStatus를 사용해야 합니다. OrderItemStatus는 주문 항목(OrderItem)의 상태를 나타내는 enum이며, Orders 엔티티는 OrderStatus를 가집니다.

🐛 수정 제안
-    Page<Orders> findByMemberIdAndStatus(Long memberId, OrderItemStatus status, Pageable pageable);
+    Page<Orders> findByMemberIdAndStatus(Long memberId, OrderStatus status, Pageable pageable);

그리고 import 수정:

-import com.kuit.baemin.domain.order.OrderItemStatus;
+import com.kuit.baemin.domain.order.OrderStatus;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Page<Orders> findByMemberIdAndStatus(Long memberId, OrderItemStatus status, Pageable pageable);
Page<Orders> findByMemberIdAndStatus(Long memberId, OrderStatus status, Pageable pageable);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/repository/OrderRepository.java` at line 11,
메서드 findByMemberIdAndStatus에서 잘못된 enum 타입을 사용하고 있으니 파라미터 타입을 OrderItemStatus에서
OrderStatus로 변경하고 관련 import 문도 OrderStatus로 교체하세요; 대상은 OrderRepository의
Page<Orders> findByMemberIdAndStatus(Long memberId, OrderStatus status, Pageable
pageable) 시그니처로 수정하는 것입니다.


boolean existsByOrderId(Long orderId);

List<Review> findByMemberIdAndStatus(Long memberId, ReviewStatus status);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

페이징 지원을 고려하세요.

회원이 많은 리뷰를 작성한 경우 모든 리뷰를 한 번에 조회하면 성능 문제가 발생할 수 있습니다. OrderRepository의 findByMemberIdAndStatus처럼 Pageable 파라미터를 추가하여 Page<Review>를 반환하도록 변경하는 것을 권장합니다.

♻️ 수정 제안
-    List<Review> findByMemberIdAndStatus(Long memberId, ReviewStatus status);
+    Page<Review> findByMemberIdAndStatus(Long memberId, ReviewStatus status, Pageable pageable);

그리고 import 추가:

+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/repository/ReviewRepository.java` at line 13,
The current ReviewRepository method findByMemberIdAndStatus(Long memberId,
ReviewStatus status) should support pagination to avoid loading all reviews at
once; change its signature to return Page<Review> and add a Pageable parameter
(e.g., Page<Review> findByMemberIdAndStatus(Long memberId, ReviewStatus status,
Pageable pageable)), and add the necessary imports for
org.springframework.data.domain.Page and
org.springframework.data.domain.Pageable so callers can pass page/size/sort.

Comment on lines +111 to +154
private OrderRes toOrderRes(Orders order) {
List<OrderItem> orderItems = orderItemRepository.findByOrderIdAndStatus(
order.getId(),
OrderItemStatus.ACTIVE
);

List<OrderItemRes> itemResponses = orderItems.stream()
.map(item -> {
List<OrderItemOptionRes> optionResponses =
orderItemOptionRepository.findByOrderItemIdAndStatus(
item.getId(),
OrderItemOptionStatus.ACTIVE
)
.stream()
.map(OrderItemOptionRes::from)
.toList();

return OrderItemRes.of(item, optionResponses);
})
.toList();

Integer totalPrice = calculateTotalPrice(orderItems);

return OrderRes.of(order, totalPrice, itemResponses);
}

private Integer calculateTotalPrice(List<OrderItem> orderItems) {
int total = 0;

for (OrderItem item : orderItems) {
List<OrderItemOption> options = orderItemOptionRepository.findByOrderItemIdAndStatus(
item.getId(),
OrderItemOptionStatus.ACTIVE
);

int optionTotal = options.stream()
.mapToInt(OrderItemOption::getAdditionalPrice)
.sum();

total += (item.getMenuPrice() + optionTotal) * item.getQuantity();
}

return total;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

N+1 쿼리 중복 발행을 개선하세요.

toOrderRes 메서드(Line 111-135)와 calculateTotalPrice 메서드(Line 137-154)에서 동일한 OrderItemOption 데이터를 각각 별도로 조회하고 있습니다:

  • Lines 119-126: 각 OrderItem에 대해 옵션을 조회하여 DTO 변환
  • Lines 141-144: 가격 계산을 위해 동일한 옵션을 재조회

이는 불필요한 중복 쿼리를 발생시켜 성능 저하를 초래합니다.

♻️ 개선 방안

방안 1: 가격 계산 로직을 DTO 변환 시점에 통합

 private OrderRes toOrderRes(Orders order) {
     List<OrderItem> orderItems = orderItemRepository.findByOrderIdAndStatus(
             order.getId(),
             OrderItemStatus.ACTIVE
     );
 
+    int totalPrice = 0;
     List<OrderItemRes> itemResponses = orderItems.stream()
             .map(item -> {
                 List<OrderItemOptionRes> optionResponses =
                         orderItemOptionRepository.findByOrderItemIdAndStatus(
                                         item.getId(),
                                         OrderItemOptionStatus.ACTIVE
                                 )
                                 .stream()
                                 .map(OrderItemOptionRes::from)
                                 .toList();
 
+                int optionTotal = optionResponses.stream()
+                        .mapToInt(opt -> opt.getAdditionalPrice())
+                        .sum();
+                totalPrice += (item.getMenuPrice() + optionTotal) * item.getQuantity();
+
                 return OrderItemRes.of(item, optionResponses);
             })
             .toList();
 
-    Integer totalPrice = calculateTotalPrice(orderItems);
-
     return OrderRes.of(order, totalPrice, itemResponses);
 }
-
-private Integer calculateTotalPrice(List<OrderItem> orderItems) {
-    int total = 0;
-
-    for (OrderItem item : orderItems) {
-        List<OrderItemOption> options = orderItemOptionRepository.findByOrderItemIdAndStatus(
-                item.getId(),
-                OrderItemOptionStatus.ACTIVE
-        );
-
-        int optionTotal = options.stream()
-                .mapToInt(OrderItemOption::getAdditionalPrice)
-                .sum();
-
-        total += (item.getMenuPrice() + optionTotal) * item.getQuantity();
-    }
-
-    return total;
-}

방안 2: Repository에서 JOIN FETCH를 사용하여 옵션을 한 번에 로드

Repository에 다음과 같은 메서드 추가:

`@Query`("SELECT oi FROM OrderItem oi " +
       "LEFT JOIN FETCH oi.orderItemOptions " +
       "WHERE oi.order.id = :orderId AND oi.status = :status")
List<OrderItem> findByOrderIdAndStatusWithOptions(
    `@Param`("orderId") Long orderId, 
    `@Param`("status") OrderItemStatus status
);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private OrderRes toOrderRes(Orders order) {
List<OrderItem> orderItems = orderItemRepository.findByOrderIdAndStatus(
order.getId(),
OrderItemStatus.ACTIVE
);
List<OrderItemRes> itemResponses = orderItems.stream()
.map(item -> {
List<OrderItemOptionRes> optionResponses =
orderItemOptionRepository.findByOrderItemIdAndStatus(
item.getId(),
OrderItemOptionStatus.ACTIVE
)
.stream()
.map(OrderItemOptionRes::from)
.toList();
return OrderItemRes.of(item, optionResponses);
})
.toList();
Integer totalPrice = calculateTotalPrice(orderItems);
return OrderRes.of(order, totalPrice, itemResponses);
}
private Integer calculateTotalPrice(List<OrderItem> orderItems) {
int total = 0;
for (OrderItem item : orderItems) {
List<OrderItemOption> options = orderItemOptionRepository.findByOrderItemIdAndStatus(
item.getId(),
OrderItemOptionStatus.ACTIVE
);
int optionTotal = options.stream()
.mapToInt(OrderItemOption::getAdditionalPrice)
.sum();
total += (item.getMenuPrice() + optionTotal) * item.getQuantity();
}
return total;
}
private OrderRes toOrderRes(Orders order) {
List<OrderItem> orderItems = orderItemRepository.findByOrderIdAndStatus(
order.getId(),
OrderItemStatus.ACTIVE
);
int totalPrice = 0;
List<OrderItemRes> itemResponses = new ArrayList<>();
for (OrderItem item : orderItems) {
List<OrderItemOptionRes> optionResponses =
orderItemOptionRepository.findByOrderItemIdAndStatus(
item.getId(),
OrderItemOptionStatus.ACTIVE
)
.stream()
.map(OrderItemOptionRes::from)
.toList();
int optionTotal = optionResponses.stream()
.mapToInt(OrderItemOptionRes::getAdditionalPrice)
.sum();
totalPrice += (item.getMenuPrice() + optionTotal) * item.getQuantity();
itemResponses.add(OrderItemRes.of(item, optionResponses));
}
return OrderRes.of(order, totalPrice, itemResponses);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/kuit/baemin/service/OrderService.java` around lines 111 -
154, toOrderRes and calculateTotalPrice are causing duplicate option queries;
fetch OrderItemOptions once and reuse them by either (A) changing the repository
call in toOrderRes to a single eager-load method (e.g., replace
orderItemRepository.findByOrderIdAndStatus(...) with a new
findByOrderIdAndStatusWithOptions(...) that JOIN FETCHes orderItemOptions) and
then use the loaded options both to build OrderItemRes (OrderItemRes.of) and to
compute price, or (B) compute each item's subtotal while mapping in toOrderRes
(use the options loaded there) and remove the separate option lookups from
calculateTotalPrice (or change calculateTotalPrice to accept the already-loaded
OrderItem list with options); update references to OrderItemOptionRepository
lookups (in toOrderRes and calculateTotalPrice) accordingly so options are
queried only once per order.

Comment on lines +3 to +5
url: jdbc:mysql://localhost:3306/baemin #jdbc:mysql://localhost:3306/{본인이 저번주차에 생성했던 DB 이름}
username: root # 본인 DB username 환경변수에 저장
password: 26072005B! # 본인 DB 비밀번호 환경변수에 저장

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

민감정보 하드코딩을 즉시 제거하세요 (Line 3, Line 4, Line 5).

password를 포함한 DB 자격증명이 저장소에 평문으로 커밋되어 보안 사고로 직결됩니다. 환경변수/시크릿 매니저로 치환하고, 이미 노출된 비밀번호는 즉시 회전(rotate)해야 합니다.

수정 예시
 spring:
   datasource:
-    url: jdbc:mysql://localhost:3306/baemin `#jdbc`:mysql://localhost:3306/{본인이 저번주차에 생성했던 DB 이름}
-    username: root # 본인 DB username 환경변수에 저장
-    password: 26072005B! # 본인 DB 비밀번호 환경변수에 저장
+    url: ${DATASOURCE_URL}
+    username: ${DATASOURCE_USERNAME}
+    password: ${DATASOURCE_PASSWORD}
     driver-class-name: com.mysql.cj.jdbc.Driver
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/application.yml` around lines 3 - 5, Remove the hardcoded
DB credentials in application.yml (the url, username, and password properties)
and replace them with environment-backed placeholders (e.g., use environment
variables for DB_URL, DB_USERNAME, DB_PASSWORD) so no plaintext secrets remain
in the repo; update any code/config that reads these properties to use the new
env vars and ensure secrets are loaded via your platform's secret manager or
environment, and immediately rotate the exposed password.

jpa:
hibernate:
ddl-auto: create # 개발 중에는 create, 이후 validate로 변경
ddl-auto: update # 개발 중에는 create, 이후 validate로 변경

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

기본 설정의 ddl-auto: update는 환경 분리 없으면 위험합니다 (Line 10).

현재 기본 application.yml에 update가 고정되어 있으면 운영/공유 DB에서 의도치 않은 스키마 변경이 발생할 수 있습니다. 최소한 profile 별로 분리해 로컬만 update, 운영은 validate(또는 none)로 제한하세요.

수정 예시
 # application.yml (공통/기본)
 spring:
   jpa:
     hibernate:
-      ddl-auto: update
+      ddl-auto: validate
# application-local.yml (로컬 전용)
spring:
  jpa:
    hibernate:
      ddl-auto: update
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/application.yml` at line 10, The default configuration
currently sets spring.jpa.hibernate.ddl-auto: update in application.yml which
risks unintended schema changes in non-local environments; change configuration
to remove/update this default and move environment-specific settings into
profile-specific files (e.g., create application-local.yml with
spring.jpa.hibernate.ddl-auto: update and ensure production/other profiles use
spring.jpa.hibernate.ddl-auto: validate or none) and ensure the active profile
selection is used at runtime so the application does not apply `update` unless
the "local" profile is explicitly active.

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.

1 participant