7주차 미션 / 서버 1조 김동현#6
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
Walkthrough회원 중심 모델을 User로 전환하고 주문·식당·메뉴·리뷰·주소 도메인을 위한 JPA 엔티티, 리포지토리, 서비스, 컨트롤러, DTO, 예외들을 대규모로 추가/갱신했습니다. 기존 MemberController/MemberService는 제거되었고 Swagger UI 의존성이 빌드에 추가되었습니다. Changes도메인, API, 서비스 전면 추가·전환 도메인, API, 서비스 전면 추가·전환
Sequence Diagram(s)sequenceDiagram
participant Client
participant UserController
participant UserService
participant UserRepository
participant Database
Client->>UserController: POST /users/signup (SignUpReq)
UserController->>UserService: signUp(req)
UserService->>UserRepository: existsByEmail(email)
UserRepository->>Database: SELECT ...
Database-->>UserRepository: result
UserRepository-->>UserService: false/true
alt exists
UserService-->>UserController: throw DUPLICATE_EMAIL
UserController-->>Client: ApiResponse(error)
else not exists
UserService->>UserRepository: save(User)
UserRepository->>Database: INSERT ...
Database-->>UserRepository: saved user
UserService-->>UserController: userId
UserController-->>Client: ApiResponse(userId)
end
sequenceDiagram
participant Client
participant OrderController
participant OrderService
participant AddressRepository
participant UserRepository
participant RestaurantRepository
participant MenuRepository
participant OrderRepository
participant OrderMenuRepository
participant Database
Client->>OrderController: POST /orders (OrderReq)
OrderController->>OrderService: createOrder(req)
OrderService->>AddressRepository: findById(addressId)
AddressRepository->>Database: SELECT address
Database-->>AddressRepository: Address
OrderService->>UserRepository: findById(userId)
UserRepository->>Database: SELECT user
Database-->>UserRepository: User
OrderService->>RestaurantRepository: findById(restaurantId)
RestaurantRepository->>Database: SELECT restaurant
Database-->>RestaurantRepository: Restaurant
OrderService->>OrderRepository: save(Order PENDING)
OrderRepository->>Database: INSERT order
Database-->>OrderRepository: saved order
loop menus
OrderService->>MenuRepository: findById(menuId)
MenuRepository->>Database: SELECT menu
Database-->>MenuRepository: Menu
OrderService->>OrderMenuRepository: save(OrderMenu)
OrderMenuRepository->>Database: INSERT order_menu
Database-->>OrderMenuRepository: saved order_menu
end
OrderService-->>OrderController: orderId
OrderController-->>Client: ApiResponse(orderId)
sequenceDiagram
participant Client
participant ReviewController
participant ReviewService
participant OrderRepository
participant ReviewRepository
participant UserRepository
participant RestaurantRepository
participant Database
Client->>ReviewController: POST /orders/{orderId}/reviews (ReviewReq)
ReviewController->>ReviewService: save(req, orderId)
ReviewService->>OrderRepository: findById(orderId)
OrderRepository->>Database: SELECT order
Database-->>OrderRepository: Order
ReviewService->>UserRepository: findById(userId)
UserRepository->>Database: SELECT user
Database-->>UserRepository: User
ReviewService->>RestaurantRepository: findById(restaurantId)
RestaurantRepository->>Database: SELECT restaurant
Database-->>RestaurantRepository: Restaurant
ReviewService->>ReviewRepository: save(Review ACTIVE)
ReviewRepository->>Database: INSERT review
Database-->>ReviewRepository: saved review
ReviewService-->>ReviewController: reviewId
ReviewController-->>Client: ApiResponse(reviewId)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
🤖 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/controller/ReviewController.java`:
- Around line 24-26: The createReview controller currently trusts the request
body for user identity; change createReview to obtain the user identifier
separately (either as a method parameter extracted from the authenticated
principal or as a separate path/query/path variable) rather than reading it from
ReviewReq, and update the call to reviewService.save(...) to accept that userId
(e.g., reviewService.save(req, orderId, userId)); then update the
ReviewService.save(...) signature to validate that the provided userId matches
authorization context and use that id for persistence/authorization checks
instead of any user field in ReviewReq.
In `@src/main/java/com/kuit/baemin/domain/Order/OrderStatus.java`:
- Line 7: The enum constant name DELIVERing in OrderStatus is misspelled and can
break serialization/comparisons; rename the enum constant to DELIVERING in the
OrderStatus enum and update all direct references/usages to
OrderStatus.DELIVERING, and if the enum is stored/serialized as a string
(API/DB) ensure any JSON or JPA mappings are adjusted—add a stable `@JsonProperty`
or `@Enumerated/String` mapping if you must preserve the previous external string
value for backwards compatibility.
In `@src/main/java/com/kuit/baemin/domain/Review/Review.java`:
- Around line 25-26: The rating field in Review (private int rating) lacks range
validation, so add validation to enforce an expected range (e.g., 1–5): enforce
it at the DTO boundary (ReviewReq) with Bean Validation annotations `@Min`(1) and
`@Max`(5) on its rating property and/or add a domain check in Review (e.g.,
validate in constructor or a setRating method) that throws a specific exception
when rating is out of range; also consider adding a database constraint via
`@Column`(columnDefinition=...) or `@Check` to reflect the same range at the DB
level.
In `@src/main/java/com/kuit/baemin/domain/User/User.java`:
- Around line 22-23: Add a DB-level unique constraint for the email field in the
User entity: update the User class's email declaration (the field named email
with `@Column`) to include unique = true (or add a `@Table`(uniqueConstraints =
`@UniqueConstraint`(...)) on the User entity) and create a corresponding DB
migration that adds a unique index/constraint for the email column,
handling/preventing existing duplicate rows as part of the migration.
In `@src/main/java/com/kuit/baemin/dto/request/OrderMenuReq.java`:
- Line 4: OrderMenuReq currently only uses `@NotNull` so invalid values like
quantity <= 0 or non-positive menuId pass validation; update the DTO fields in
class OrderMenuReq (e.g., the quantity and menuId properties) to use proper
numeric constraints such as `@Positive` or `@Min`(1) together with `@NotNull`, and add
the necessary import(s) from jakarta.validation.constraints (e.g., Positive or
Min) and remove the stray duplicate semicolon in the import line so the
annotations are recognized at runtime.
In `@src/main/java/com/kuit/baemin/dto/request/OrderReq.java`:
- Around line 20-21: OrderReq의 menus 필드에는 현재 `@NotNull만` 있어 내부 객체 검증과 빈 리스트 방지를
못하므로, menus에 빈 리스트 검사 어노테이션(`@NotEmpty` 또는 `@Size`(min=1))을 추가하고 리스트 요소들에 대한 검증이
실행되도록 `@Valid를` 붙이세요; 대상 식별자: 클래스 OrderReq, 필드 menus, 요소 타입 OrderMenuReq (필요시
javax.validation과 spring validation 어노테이션을 import).
In `@src/main/java/com/kuit/baemin/dto/request/UserUpdateReq.java`:
- Around line 17-18: The phoneNumber validation in UserUpdateReq currently
requires hyphens via the `@Pattern` on field phoneNumber; change this to accept
both hyphenated and non-hyphenated formats by updating the `@Pattern` to allow
optional hyphens (so strings like 01012345678 or 010-1234-5678 pass), or
alternatively normalize input in the service layer (e.g., in
UserService.updateUser or a normalizePhoneNumber helper) by stripping non-digit
characters before validation/persistence and then store a consistent formatted
value; update the annotation or add the normalization step and corresponding
unit tests accordingly.
In `@src/main/java/com/kuit/baemin/dto/response/MenuRes.java`:
- Line 4: Remove the unused import com.kuit.baemin.domain.Menu.MenuStatus from
the MenuRes DTO; edit the MenuRes class to delete that import line (it’s unused)
so only necessary imports remain and ensure the class still compiles without
referencing MenuStatus.
In `@src/main/java/com/kuit/baemin/dto/response/OrderRes.java`:
- Line 4: The import com.kuit.baemin.domain.Order.OrderStatus in OrderRes is
unused; remove that import line (or run your IDE's Organize/Optimize Imports) so
OrderRes no longer imports OrderStatus unnecessarily.
In `@src/main/java/com/kuit/baemin/dto/response/UserRes.java`:
- Line 18: Remove the sensitive password field from the response DTO UserRes:
delete the private String password declaration and any associated
getters/setters or builder parameters in UserRes, and update any mapping or
conversion code that populates UserRes (e.g., methods that construct UserRes
from a User entity) so they no longer attempt to set or expose the password;
ensure tests/serializers referencing UserRes.password are updated or removed.
In `@src/main/java/com/kuit/baemin/exception/errorcode/ErrorStatus.java`:
- Around line 25-43: The enum entries REVIEW_ORDER_MISMATCH and REVIEW_NOT_OWNER
are semantically misaligned with their messages; update them so names match
messages — either swap their message strings or rename the constants.
Specifically, ensure REVIEW_ORDER_MISMATCH has the message "해당 주문에 속한 리뷰가 아닙니다."
(order/relation mismatch) and REVIEW_NOT_OWNER has "본인이 작성한 리뷰가 아닙니다." (not the
review owner), by editing these enum entries in ErrorStatus.
In `@src/main/java/com/kuit/baemin/repository/MenuRepository.java`:
- Line 13: The repository method parameter uses snake_case; rename the parameter
in the MenuRepository method declaration List<Menu>
findByRestaurantIdAndStatus(Long restaurant_id, MenuStatus status, Pageable
pageable) to use camelCase (restaurantId) so it reads List<Menu>
findByRestaurantIdAndStatus(Long restaurantId, MenuStatus status, Pageable
pageable); update any local usages or references to the parameter name in the
interface or implementing code if present.
In `@src/main/java/com/kuit/baemin/repository/OrderMenuRepository.java`:
- Line 11: OrderMenuRepository's findByOrderId causes an N+1 when
OrderMenuRes.from() accesses orderMenu.getMenu().getName(); update findByOrderId
in OrderMenuRepository to eagerly fetch the menu to avoid repeated
queries—either replace the method with a JPQL query using JOIN FETCH to load
OrderMenu with its menu in one query or annotate the existing findByOrderId with
an `@EntityGraph`(attributePaths = {"menu"}) so OrderMenu.menu is fetched
together; ensure the method signature still accepts Long orderId and returns
List<OrderMenu>.
In `@src/main/java/com/kuit/baemin/repository/UserRepository.java`:
- Line 18: UserRepository currently exposes only existsByEmail(String email)
which will treat a user's unchanged email as a duplicate during updates; add a
new method signature existsByEmailAndIdNot(Long id, String email) to
UserRepository and update the user-update flow to call
existsByEmailAndIdNot(userId, email) instead of existsByEmail(email) so the
check excludes the current user's id. Ensure method name matches Spring Data JPA
naming (existsByEmailAndIdNot) and keep the original existsByEmail for create
flows.
In `@src/main/java/com/kuit/baemin/service/MenuService.java`:
- Around line 23-29: The current MenuService.findByRestaurantId returns an empty
list when restaurantId doesn't exist; inject RestaurantRepository into
MenuService and pre-validate the restaurant by calling
restaurantRepository.findById(restaurantId).orElseThrow(() -> new
GeneralException(ErrorStatus.RESTAURANT_NOT_FOUND)) before querying
menuRepository.findByRestaurantIdAndStatus(...), so non-existent restaurants
throw the proper GeneralException (use the existing GeneralException and
ErrorStatus.RESTAURANT_NOT_FOUND symbols).
In `@src/main/java/com/kuit/baemin/service/RestaurantService.java`:
- Around line 31-33: Remove the unused Javadoc block above the 메뉴 목록 comment in
the RestaurantService class: delete the empty/unused Javadoc comment (the /**
... */ block) in src/main/java/com/kuit/baemin/service/RestaurantService.java so
the class no longer contains that redundant comment (menu listing is handled by
MenuService); leave the class and any other real documentation intact.
In `@src/main/java/com/kuit/baemin/service/UserService.java`:
- Around line 95-99: The duplicate-email check rejects the same user's unchanged
email because it only calls userRepository.existsByEmail(req.getEmail()); change
it to ignore the current user: when req.getEmail() is non-null, look up the user
by email (or use userRepository.existsByEmailAndIdNot(email, currentUserId)) and
only throw DUPLICATE_EMAIL if the found user's id differs from the current
user's id; reference the existing symbols req, userRepository.existsByEmail (or
add existsByEmailAndIdNot), and the DUPLICATE_EMAIL exception in UserService.
- Around line 35-38: The service is storing and comparing plaintext passwords;
add Spring Security dependency (spring-boot-starter-security) and introduce a
PasswordEncoder (e.g., BCryptPasswordEncoder) bean, inject it into UserService,
and use encoder.encode(req.getPassword()) when building/saving User (referencing
UserService and the User.builder(...) call) and replace direct equality checks
with encoder.matches(rawPassword, user.getPassword()) in authentication/update
methods (the methods around the comparisons at lines mentioned). Ensure the
encoder is wired via constructor injection in UserService and used wherever
password verification or assignment occurs.
🪄 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: 520a260a-d65e-4d53-8c5d-912a356f6734
📒 Files selected for processing (51)
build.gradlesrc/main/java/com/kuit/baemin/controller/MemberController.javasrc/main/java/com/kuit/baemin/controller/OrderController.javasrc/main/java/com/kuit/baemin/controller/RestaurantController.javasrc/main/java/com/kuit/baemin/controller/ReviewController.javasrc/main/java/com/kuit/baemin/controller/UserController.javasrc/main/java/com/kuit/baemin/domain/Address/Address.javasrc/main/java/com/kuit/baemin/domain/Address/AddressStatus.javasrc/main/java/com/kuit/baemin/domain/Address/AddressType.javasrc/main/java/com/kuit/baemin/domain/Menu/Menu.javasrc/main/java/com/kuit/baemin/domain/Menu/MenuStatus.javasrc/main/java/com/kuit/baemin/domain/Order/Order.javasrc/main/java/com/kuit/baemin/domain/Order/OrderStatus.javasrc/main/java/com/kuit/baemin/domain/OrderMenu/OrderMenu.javasrc/main/java/com/kuit/baemin/domain/OrderMenu/OrderMenuStatus.javasrc/main/java/com/kuit/baemin/domain/Restaurant/Restaurant.javasrc/main/java/com/kuit/baemin/domain/Review/Review.javasrc/main/java/com/kuit/baemin/domain/Review/ReviewStatus.javasrc/main/java/com/kuit/baemin/domain/User/User.javasrc/main/java/com/kuit/baemin/domain/User/UserStatus.javasrc/main/java/com/kuit/baemin/domain/member/Member.javasrc/main/java/com/kuit/baemin/dto/request/OrderMenuReq.javasrc/main/java/com/kuit/baemin/dto/request/OrderReq.javasrc/main/java/com/kuit/baemin/dto/request/ReviewReq.javasrc/main/java/com/kuit/baemin/dto/request/UserUpdateReq.javasrc/main/java/com/kuit/baemin/dto/response/MemberRes.javasrc/main/java/com/kuit/baemin/dto/response/MenuRes.javasrc/main/java/com/kuit/baemin/dto/response/OrderMenuRes.javasrc/main/java/com/kuit/baemin/dto/response/OrderRes.javasrc/main/java/com/kuit/baemin/dto/response/RestaurantRes.javasrc/main/java/com/kuit/baemin/dto/response/UserRes.javasrc/main/java/com/kuit/baemin/exception/AddressException.javasrc/main/java/com/kuit/baemin/exception/MenuException.javasrc/main/java/com/kuit/baemin/exception/OrderException.javasrc/main/java/com/kuit/baemin/exception/RestaurantException.javasrc/main/java/com/kuit/baemin/exception/ReviewException.javasrc/main/java/com/kuit/baemin/exception/UserException.javasrc/main/java/com/kuit/baemin/exception/errorcode/ErrorStatus.javasrc/main/java/com/kuit/baemin/repository/AddressRepository.javasrc/main/java/com/kuit/baemin/repository/MenuRepository.javasrc/main/java/com/kuit/baemin/repository/OrderMenuRepository.javasrc/main/java/com/kuit/baemin/repository/OrderRepository.javasrc/main/java/com/kuit/baemin/repository/RestaurantRepository.javasrc/main/java/com/kuit/baemin/repository/ReviewRepository.javasrc/main/java/com/kuit/baemin/repository/UserRepository.javasrc/main/java/com/kuit/baemin/service/MemberService.javasrc/main/java/com/kuit/baemin/service/MenuService.javasrc/main/java/com/kuit/baemin/service/OrderService.javasrc/main/java/com/kuit/baemin/service/RestaurantService.javasrc/main/java/com/kuit/baemin/service/ReviewService.javasrc/main/java/com/kuit/baemin/service/UserService.java
💤 Files with no reviewable changes (4)
- src/main/java/com/kuit/baemin/service/MemberService.java
- src/main/java/com/kuit/baemin/dto/response/MemberRes.java
- src/main/java/com/kuit/baemin/domain/member/Member.java
- src/main/java/com/kuit/baemin/controller/MemberController.java
| public ApiResponse<Long> createReview(@Valid @RequestBody ReviewReq req, | ||
| @PathVariable Long orderId) { | ||
| return ApiResponse.onSuccess(reviewService.save(req, orderId)); |
There was a problem hiding this comment.
리뷰 작성 API에서 클라이언트 본문의 사용자 식별자를 신뢰하면 권한 우회 위험이 있습니다.
현재 createReview는 userId를 별도 파라미터로 받지 않아(또는 인증 컨텍스트에서 추출하지 않아) 요청 본문의 식별자 신뢰로 이어집니다. 삭제 API와 동일하게 사용자 식별자를 분리 전달받고, 서비스에서 그 값을 기준으로 검증하도록 맞추는 것이 안전합니다.
🔧 제안 수정안
- public ApiResponse<Long> createReview(`@Valid` `@RequestBody` ReviewReq req,
- `@PathVariable` Long orderId) {
- return ApiResponse.onSuccess(reviewService.save(req, orderId));
+ public ApiResponse<Long> createReview(`@Valid` `@RequestBody` ReviewReq req,
+ `@PathVariable` Long orderId,
+ `@RequestParam` Long userId) {
+ return ApiResponse.onSuccess(reviewService.save(req, orderId, userId));
}🤖 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/controller/ReviewController.java` around lines
24 - 26, The createReview controller currently trusts the request body for user
identity; change createReview to obtain the user identifier separately (either
as a method parameter extracted from the authenticated principal or as a
separate path/query/path variable) rather than reading it from ReviewReq, and
update the call to reviewService.save(...) to accept that userId (e.g.,
reviewService.save(req, orderId, userId)); then update the
ReviewService.save(...) signature to validate that the provided userId matches
authorization context and use that id for persistence/authorization checks
instead of any user field in ReviewReq.
| PENDING, // 주문 접수 대기 | ||
| ACCEPTED, // 주문 수락 | ||
| COOKING, // 조리 중 | ||
| DELIVERing, // 배달 중 |
There was a problem hiding this comment.
Enum 상태값 오탈자(DELIVERing)는 직렬화/비교 오류를 유발합니다.
Line 7 상수명은 DELIVERING으로 수정하는 것이 안전합니다. 현재 값은 API/DB 문자열 값으로 그대로 퍼질 수 있습니다.
✏️ 제안 수정안
- DELIVERing, // 배달 중
+ DELIVERING, // 배달 중📝 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.
| DELIVERing, // 배달 중 | |
| DELIVERING, // 배달 중 |
🤖 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/OrderStatus.java` at line 7, The
enum constant name DELIVERing in OrderStatus is misspelled and can break
serialization/comparisons; rename the enum constant to DELIVERING in the
OrderStatus enum and update all direct references/usages to
OrderStatus.DELIVERING, and if the enum is stored/serialized as a string
(API/DB) ensure any JSON or JPA mappings are adjusted—add a stable `@JsonProperty`
or `@Enumerated/String` mapping if you must preserve the previous external string
value for backwards compatibility.
| @Column(nullable = false) | ||
| private int rating; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
rating 필드에 범위 검증을 추가하세요.
rating이 int 타입으로 선언되어 있어 음수나 과도하게 큰 값이 저장될 수 있습니다. 일반적으로 평점은 15 또는 110 범위로 제한됩니다. JPA @Column의 columnDefinition이나 별도 검증 로직을 추가하는 것을 권장합니다.
🛡️ 제안하는 수정 (예: 1~5 범위)
엔티티 레벨에서는 제약을 명시하기 어려우므로, 서비스 레이어나 DTO 검증에서 처리:
DTO 검증 (ReviewReq):
`@Min`(1)
`@Max`(5)
private int rating;또는 도메인 검증 로직:
+ public void validateRating() {
+ if (rating < 1 || rating > 5) {
+ throw new GeneralException(ErrorStatus.INVALID_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 25 -
26, The rating field in Review (private int rating) lacks range validation, so
add validation to enforce an expected range (e.g., 1–5): enforce it at the DTO
boundary (ReviewReq) with Bean Validation annotations `@Min`(1) and `@Max`(5) on its
rating property and/or add a domain check in Review (e.g., validate in
constructor or a setRating method) that throws a specific exception when rating
is out of range; also consider adding a database constraint via
`@Column`(columnDefinition=...) or `@Check` to reflect the same range at the DB
level.
| User user = User.builder() | ||
| .email(req.getEmail()) | ||
| .password(req.getPassword()) | ||
| .phoneNumber(req.getPhoneNumber()) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# PasswordEncoder 도입 여부와 평문 비교 패턴을 읽기 전용으로 점검합니다.
rg -nP --type=java -C3 '\bPasswordEncoder\b|BCryptPasswordEncoder\b|\.encode\s*\(|\.matches\s*\('
rg -nP --type=java -C3 '\.getPassword\(\)\.equals\s*\('Repository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 748
🏁 Script executed:
# UserService.java 파일 읽기 (크기 확인 후 전체 또는 부분 읽기)
wc -l src/main/java/com/kuit/baemin/service/UserService.javaRepository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 129
🏁 Script executed:
# UserService.java 전체 내용 확인
cat -n src/main/java/com/kuit/baemin/service/UserService.javaRepository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 3946
🏁 Script executed:
# Spring Security 의존성 확인
find . -name "pom.xml" -o -name "build.gradle" | head -5 | xargs grep -l "spring-security\|spring.security" 2>/dev/nullRepository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 54
🏁 Script executed:
# 프로젝트 전체에서 PasswordEncoder 재검색 (import 포함)
rg -n "PasswordEncoder|BCryptPasswordEncoder|@Bean.*PasswordEncoder" --type=javaRepository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 52
🏁 Script executed:
# pom.xml 위치 찾기
find . -name "pom.xml" -type f | head -3Repository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 52
🏁 Script executed:
# build.gradle 위치 찾기
find . -name "build.gradle" -type f | head -3Repository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 85
🏁 Script executed:
# 프로젝트 루트에서 의존성 파일 확인
ls -la | grep -E "pom|build|gradle"Repository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 371
🏁 Script executed:
# build.gradle 내용 확인
cat -n build.gradleRepository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 1450
🏁 Script executed:
# Spring Security 의존성 확인
rg "spring-security|security" build.gradleRepository: Konkuk-KUIT/KUIT7_REST-API
Length of output: 52
비밀번호를 평문으로 저장/비교하고 있어 즉시 수정이 필요합니다.
Line 35-38, Line 56-57, Line 107에서 비밀번호를 평문으로 저장하고 비교하고 있습니다. 계정 탈취/유출 리스크가 커서 릴리즈 전 수정이 필수입니다.
🔐 제안 수정안
먼저 build.gradle에 Spring Security 의존성을 추가합니다:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
}그 후 UserService를 다음과 같이 수정합니다:
import com.kuit.baemin.repository.UserRepository;
+import org.springframework.security.crypto.password.PasswordEncoder;
...
public class UserService {
private final UserRepository userRepository;
+ private final PasswordEncoder passwordEncoder;
...
User user = User.builder()
.email(req.getEmail())
- .password(req.getPassword())
+ .password(passwordEncoder.encode(req.getPassword()))
.phoneNumber(req.getPhoneNumber())
.name(req.getNickname())
.status(UserStatus.ACTIVE)
.build();
...
- if (!user.getPassword().equals(req.getPassword())) {
+ if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
throw new UserException(INVALID_PASSWORD);
}
...
if (req.getPassword() != null)
- user.updatePassword(req.getPassword());
+ user.updatePassword(passwordEncoder.encode(req.getPassword()));🤖 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/UserService.java` around lines 35 - 38,
The service is storing and comparing plaintext passwords; add Spring Security
dependency (spring-boot-starter-security) and introduce a PasswordEncoder (e.g.,
BCryptPasswordEncoder) bean, inject it into UserService, and use
encoder.encode(req.getPassword()) when building/saving User (referencing
UserService and the User.builder(...) call) and replace direct equality checks
with encoder.matches(rawPassword, user.getPassword()) in authentication/update
methods (the methods around the comparisons at lines mentioned). Ensure the
encoder is wired via constructor injection in UserService and used wherever
password verification or assignment occurs.
- OrderService에서 주문 생성 시 존재하지 않는 userId로도 주문 가능했던 문제 수정 - ErrorStatus 에서 서로 메시지가 바뀐 에러 코드의 메시지 수정 - ReviewService의 주문 생성/삭제에서 회원 검증 추가 - ReviewService의 주문 삭제에서 리뷰 삭제 여부, 리뷰 존재 여부 검증 추가 - MenuService의 식당 메뉴 목록 조회에서 식당 존재 여부 검증 추가 - 주석 수정
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
src/main/java/com/kuit/baemin/controller/ReviewController.java (1)
24-26:⚠️ Potential issue | 🟠 Major | ⚡ Quick win리뷰 작성 시 사용자 인증 정보를 요청 본문이 아닌 별도 파라미터로 받아야 합니다.
현재
createReview는ReviewReq본문의userId를 신뢰하여 클라이언트가 임의의 사용자로 리뷰를 작성할 수 있습니다.deleteReview(Line 36)처럼userId를@RequestParam으로 분리하여 받고, 향후 JWT 인증으로 대체할 수 있도록 구조를 통일해야 합니다.🔒 사용자 파라미터 분리
`@PostMapping` `@Operation`(summary = "리뷰 작성") public ApiResponse<Long> createReview(`@Valid` `@RequestBody` ReviewReq req, - `@PathVariable` Long orderId) { - return ApiResponse.onSuccess(reviewService.save(req, orderId)); + `@PathVariable` Long orderId, + `@RequestParam` Long userId) { + return ApiResponse.onSuccess(reviewService.save(req, orderId, userId)); }그리고
ReviewService.save메서드 시그니처도userId를 별도 파라미터로 받도록 수정 필요합니다.🤖 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/controller/ReviewController.java` around lines 24 - 26, The createReview method currently trusts userId inside ReviewReq; change its signature to accept the authenticated userId as a separate parameter (e.g., add `@RequestParam` Long userId similar to deleteReview) and stop reading userId from ReviewReq, then update the call to reviewService.save to pass that userId (e.g., reviewService.save(req, orderId, userId)); also update the ReviewService.save method signature to accept the separate userId parameter and adjust implementations accordingly so future JWT-based auth can replace the `@RequestParam`.
🤖 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/service/OrderService.java`:
- Around line 79-100: The loop currently saves each OrderMenu individually
(orderMenuRepository.save(orderMenu)) causing N DB calls; instead, collect
OrderMenu instances into a List<OrderMenu> while performing the same validation
using menuRepository.findById(...) and comparing menu.getRestaurant().getId() to
req.getRestaurantId(), then after the loop call
orderMenuRepository.saveAll(collectedOrderMenus) to persist in a single batch;
ensure you still set order(saved), price(menu.getPrice()),
quantity(menuReq.getQuantity()), and status(OrderMenuStatus.ACTIVE) on each
OrderMenu before calling saveAll.
In `@src/main/java/com/kuit/baemin/service/ReviewService.java`:
- Around line 47-51: In ReviewService where you validate order ownership (the
block comparing order.getUser().getId() to req.getUserId()), replace the
incorrect REVIEW_NOT_OWNER error code with the correct ORDER_NOT_OWNER error
status when throwing the exception so the thrown error matches "사용자의 주문이 아닙니다.";
update the throw statement that currently uses REVIEW_NOT_OWNER to use
ORDER_NOT_OWNER (keeping the same exception type/constructor usage as
surrounding code).
- Around line 65-66: You are re-querying the same Restaurant twice: replace the
second lookup
restaurantRepository.findById(req.getRestaurantId()).orElseThrow(... ) with the
Restaurant already available from order.getRestaurant(); use that instance when
building the Review (in ReviewService where you currently call
restaurantRepository.findById(...)) to avoid the redundant DB hit and remove the
unnecessary RESTAURANT_NOT_FOUND branch for that path.
---
Duplicate comments:
In `@src/main/java/com/kuit/baemin/controller/ReviewController.java`:
- Around line 24-26: The createReview method currently trusts userId inside
ReviewReq; change its signature to accept the authenticated userId as a separate
parameter (e.g., add `@RequestParam` Long userId similar to deleteReview) and stop
reading userId from ReviewReq, then update the call to reviewService.save to
pass that userId (e.g., reviewService.save(req, orderId, userId)); also update
the ReviewService.save method signature to accept the separate userId parameter
and adjust implementations accordingly so future JWT-based auth can replace the
`@RequestParam`.
🪄 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: 5eadbe21-b8bd-41c9-87c7-d06a5cf5b4fd
📒 Files selected for processing (7)
src/main/java/com/kuit/baemin/controller/OrderController.javasrc/main/java/com/kuit/baemin/controller/RestaurantController.javasrc/main/java/com/kuit/baemin/controller/ReviewController.javasrc/main/java/com/kuit/baemin/exception/errorcode/ErrorStatus.javasrc/main/java/com/kuit/baemin/service/MenuService.javasrc/main/java/com/kuit/baemin/service/OrderService.javasrc/main/java/com/kuit/baemin/service/ReviewService.java
- email DB 단계에서 유니크 제약 추가 - OrderMenuReq의 menuId, quantity 하한 검증 추가 (@positive 어노테이션) - 업데이트용 이메일 중복 검사 메서드 분리 (boolean existsByEmailAndIdNot(String email, Long id)) - 주문 메뉴 저장 시 배치 저장 - 주문 소유권 검증 에러 코드 수정 - 식당 조회 중복 제거
- 주석 제거, 변수명 수정, 미사용 import 제거
- @EntityGraph 를 활용해 N+1 문제 해결
- findByOrderId로 조회한 OrderMenu 목록을 처리할 때 각 항목의 orderMenu.getMenu().getName()에 접근하면서 N+1 쿼리 문제 발생
- OrderService의 getOrder 메서드에서 OrderRes.from()을 호출할 때 order.getUser().getName(), order.getRestaurant().getName() 에서도 발생
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/main/java/com/kuit/baemin/service/UserService.java (1)
35-37:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win비밀번호가 여전히 평문으로 저장/비교되고 있습니다.
Line 37(회원가입), Line 56(로그인), Line 107(정보수정)에서 비밀번호를 평문으로 처리하고 있어 계정 탈취 및 유출 리스크가 매우 큽니다.
PasswordEncoder(BCrypt 등)를 도입해 암호화 저장 및 검증으로 전환해야 합니다.Also applies to: 56-56, 107-107
🤖 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/UserService.java` around lines 35 - 37, The UserService is storing and comparing plain-text passwords; change it to use a PasswordEncoder (e.g., BCryptPasswordEncoder) bean: inject PasswordEncoder into UserService and call passwordEncoder.encode(...) when building the User in the user creation path (replace the plain assignment at User.builder().password(...)), use passwordEncoder.matches(rawPassword, storedEncodedPassword) in the login method instead of direct equality, and encode the new password before saving in the user update method (replace the plain assignment at the update path). Ensure you reference and update the methods where User.builder().password(...) and any direct password comparisons occur so stored passwords are encoded and verification uses matches().src/main/java/com/kuit/baemin/dto/request/OrderMenuReq.java (1)
5-5:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winimport 문에 불필요한 세미콜론이 중복되어 있습니다.
Line 5의 import 문 끝에 세미콜론이 두 개 있습니다. 컴파일러가 허용하지만 코드 스타일 위반입니다.
🧹 제안 수정
-import jakarta.validation.constraints.NotNull;; +import jakarta.validation.constraints.NotNull;🤖 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/OrderMenuReq.java` at line 5, Remove the duplicated semicolon at the end of the import statement in OrderMenuReq (the line importing jakarta.validation.constraints.NotNull) so the import reads with a single terminating semicolon; update the import in class OrderMenuReq to follow standard Java style.
🤖 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/User/User.java`:
- Around line 35-52: The update methods on the User entity (updateStatus,
updateName, updateEmail, updatePhoneNumber, updatePassword) perform direct
assignment with no validation; add defensive validation inside each method to
enforce invariants (e.g., non-null/non-blank for name/email/password, valid
email format for updateEmail, phone format for updatePhoneNumber, minimum
complexity/length for updatePassword, and non-null for updateStatus) and throw
an appropriate unchecked exception (IllegalArgumentException or a
domain-specific exception) when validation fails so the domain guarantees remain
enforced regardless of caller; implement checks in the corresponding methods
(updateName, updateEmail, updatePhoneNumber, updatePassword, updateStatus)
rather than relying solely on service-layer validation.
In `@src/main/java/com/kuit/baemin/service/ReviewService.java`:
- Around line 37-51: The save/delete methods in ReviewService are using
client-supplied req.getUserId()/userId for authorization which allows ID
spoofing; change them to derive the authenticated user ID from the security
context (e.g., JWT principal) rather than request body/params. Update
ReviewService.save and the delete method to accept the authenticated principal
or call SecurityContextHolder (or a passed-in AuthenticatedUser id) and use that
id for userRepository.findById and owner checks (replace uses of req.getUserId()
and the method parameter userId). Ensure all authorization checks
(order.getUser().getId().equals(...), etc.) compare against the authenticated id
only and remove trust of client-provided userId.
---
Duplicate comments:
In `@src/main/java/com/kuit/baemin/dto/request/OrderMenuReq.java`:
- Line 5: Remove the duplicated semicolon at the end of the import statement in
OrderMenuReq (the line importing jakarta.validation.constraints.NotNull) so the
import reads with a single terminating semicolon; update the import in class
OrderMenuReq to follow standard Java style.
In `@src/main/java/com/kuit/baemin/service/UserService.java`:
- Around line 35-37: The UserService is storing and comparing plain-text
passwords; change it to use a PasswordEncoder (e.g., BCryptPasswordEncoder)
bean: inject PasswordEncoder into UserService and call
passwordEncoder.encode(...) when building the User in the user creation path
(replace the plain assignment at User.builder().password(...)), use
passwordEncoder.matches(rawPassword, storedEncodedPassword) in the login method
instead of direct equality, and encode the new password before saving in the
user update method (replace the plain assignment at the update path). Ensure you
reference and update the methods where User.builder().password(...) and any
direct password comparisons occur so stored passwords are encoded and
verification uses matches().
🪄 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: 53f77e93-58a5-4981-a251-83e74cf116d6
📒 Files selected for processing (12)
src/main/java/com/kuit/baemin/domain/User/User.javasrc/main/java/com/kuit/baemin/dto/request/OrderMenuReq.javasrc/main/java/com/kuit/baemin/dto/request/OrderReq.javasrc/main/java/com/kuit/baemin/dto/response/MenuRes.javasrc/main/java/com/kuit/baemin/repository/MenuRepository.javasrc/main/java/com/kuit/baemin/repository/OrderMenuRepository.javasrc/main/java/com/kuit/baemin/repository/OrderRepository.javasrc/main/java/com/kuit/baemin/repository/UserRepository.javasrc/main/java/com/kuit/baemin/service/OrderService.javasrc/main/java/com/kuit/baemin/service/RestaurantService.javasrc/main/java/com/kuit/baemin/service/ReviewService.javasrc/main/java/com/kuit/baemin/service/UserService.java
| public void updateStatus(UserStatus status) { | ||
| this.status = status; | ||
| } | ||
|
|
||
| public void updateName(String name) { | ||
| this.name = name; | ||
| } | ||
| public void updateEmail(String email) { | ||
| this.email = email; | ||
| } | ||
|
|
||
| public void updatePhoneNumber(String phoneNumber) { | ||
| this.phoneNumber = phoneNumber; | ||
| } | ||
|
|
||
| public void updatePassword(String password) { | ||
| this.password = password; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
update 메서드들에 입력 검증이 없습니다.
updateEmail, updateName 등 모든 update 메서드가 null 체크나 형식 검증 없이 필드를 직접 갱신합니다. 서비스 계층에서 검증이 이루어지긴 하지만, 도메인 모델 자체에 불변 조건 검증을 추가하면 더 안전합니다.
🛡️ 제안하는 방어 로직
public void updateEmail(String email) {
+ if (email == null || email.isBlank()) {
+ throw new IllegalArgumentException("이메일은 필수입니다");
+ }
this.email = email;
}📝 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.
| public void updateStatus(UserStatus status) { | |
| this.status = status; | |
| } | |
| public void updateName(String name) { | |
| this.name = name; | |
| } | |
| public void updateEmail(String email) { | |
| this.email = email; | |
| } | |
| public void updatePhoneNumber(String phoneNumber) { | |
| this.phoneNumber = phoneNumber; | |
| } | |
| public void updatePassword(String password) { | |
| this.password = password; | |
| } | |
| public void updateStatus(UserStatus status) { | |
| this.status = status; | |
| } | |
| public void updateName(String name) { | |
| this.name = name; | |
| } | |
| public void updateEmail(String email) { | |
| if (email == null || email.isBlank()) { | |
| throw new IllegalArgumentException("이메일은 필수입니다"); | |
| } | |
| this.email = email; | |
| } | |
| public void updatePhoneNumber(String phoneNumber) { | |
| this.phoneNumber = phoneNumber; | |
| } | |
| public void updatePassword(String password) { | |
| this.password = password; | |
| } |
🤖 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/User/User.java` around lines 35 - 52,
The update methods on the User entity (updateStatus, updateName, updateEmail,
updatePhoneNumber, updatePassword) perform direct assignment with no validation;
add defensive validation inside each method to enforce invariants (e.g.,
non-null/non-blank for name/email/password, valid email format for updateEmail,
phone format for updatePhoneNumber, minimum complexity/length for
updatePassword, and non-null for updateStatus) and throw an appropriate
unchecked exception (IllegalArgumentException or a domain-specific exception)
when validation fails so the domain guarantees remain enforced regardless of
caller; implement checks in the corresponding methods (updateName, updateEmail,
updatePhoneNumber, updatePassword, updateStatus) rather than relying solely on
service-layer validation.
| public Long save(ReviewReq req, Long orderId) { | ||
|
|
||
| // 검증 순서: user -> order -> restaurant 순 | ||
| // 존재하는 회원인지 검증 | ||
| User user = userRepository.findById(req.getUserId()) | ||
| .orElseThrow(() -> new UserException(USER_NOT_FOUND)); | ||
|
|
||
| // 본인의 주문인지 검증 | ||
| Order order = orderRepository.findById(orderId) | ||
| .orElseThrow(() -> new OrderException(ORDER_NOT_FOUND)); | ||
| if (!order.getUser() | ||
| .getId() | ||
| .equals(req.getUserId())) { | ||
| throw new OrderException(ORDER_NOT_OWNER); | ||
| } |
There was a problem hiding this comment.
클라이언트 제공 userId 신뢰로 인한 권한 우회 위험
save/delete 모두 인증 주체를 요청값(req.userId, userId)에서 받아 권한 검증의 기준으로 사용하고 있습니다. 이 방식은 userId 위조 시 타인 주문 리뷰 작성/삭제로 이어질 수 있으니, userId는 요청 파라미터/바디가 아니라 인증 컨텍스트(예: JWT principal)에서만 주입받도록 바꿔야 합니다.
Also applies to: 77-102
🤖 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/ReviewService.java` around lines 37 -
51, The save/delete methods in ReviewService are using client-supplied
req.getUserId()/userId for authorization which allows ID spoofing; change them
to derive the authenticated user ID from the security context (e.g., JWT
principal) rather than request body/params. Update ReviewService.save and the
delete method to accept the authenticated principal or call
SecurityContextHolder (or a passed-in AuthenticatedUser id) and use that id for
userRepository.findById and owner checks (replace uses of req.getUserId() and
the method parameter userId). Ensure all authorization checks
(order.getUser().getId().equals(...), etc.) compare against the authenticated id
only and remove trust of client-provided userId.
구현한 API 목록
수정 내용 (피드백 반영)
1. 테이블명 예약어 충돌
2. 리뷰 작성/삭제 권한 검증
REVIEW_OWNER_MISMATCH)REVIEW_RESTAURANT_MISMATCH)REVIEW_OWNER_MISMATCH)REVIEW_ORDER_MISMATCH)@RequestParam으로 userId 추가 (8주차 JWT 이후 토큰 추출로 교체 가능)3. 회원 정보 수정 시 이메일 중복 확인 누락
updateUser()에서 이메일 변경 시 중복 확인 없음userRepository.existsByEmail()중복 확인 추가4. 주문 생성 시 메뉴 소속 식당 검증 누락
menu.getRestaurant().getId().equals(req.getRestaurantId())검증 추가 (MENU_RESTAURANT_MISMATCH)기타
느낀점
스터디 시간에 강희진 서버 파트장님께서 귀한 시간 내주셔서 피드백을 해주셨습니다. 위 '수정 내용' 부분의 1, 2번 내용에 해당합니다. 급하게 미션을 진행하려다보니 기본적인 처리를 테스트하지 못하고 제출했던 점을 반성하고 있습니다.
그리고 피드백을 받았던 내용에 더해서, 회원 정보를 수정할 때 이메일을 수정하는 경우에도 다시 이메일 검증을 수행하도록 수정했으며, 주문을 요청할 때 주문하려는 메뉴가 해당 식당의 메뉴인지 확인하는 검증을 수행하도록 하는 등 추가적인 개선점을 발견하고 수정하였습니다.
그리고 가장 중요한 문제점을 이번에 확실히 깨닫게 되었습니다.. 이번 주차 녹화본 영상을 봤었을 때 환경변수 설정을 어떻게 하는지 몰라 직접 작성했던 것, 그리고 push 할 때 제대로 확인하지 않은 점이 문제였던 것 같습니다. 물론 제일 문제인 것은 이러한 점을 제대로 확인하지 않고 진행했던 저의 잘못임을 잘 알고 있습니다. 이번 기회를 통해 직접 깨달았기 때문에 다음에는 같은 일이 발생하지 않을 것이라고 생각합니다. 그렇게 하도록 신경을 잘 쓰겠습니다..
+++++
추가로 Postman API 명세서를 작성하면서 무려 7가지의 검증 누락을 발견했습니다. 물론 처음에 API를 구현하면서 생각할 수 있던 사항들이었지만 간단한 테스트 과정에서 오류를 발견했던 좋은 경험인 것 같습니다. 그리고 이번에 API 명세서를 작성해보면서 Postman이라는 기술도 배울 수 있어 좋았습니다. 다만 아직 발견하지 못한 검증 누락이나 다른 에러가 있을 수도 있어서, 기회가 된다면 더 개선해 나가면서 찾아내고 싶습니다.
Notion에 추가했던 Postman URL을 쉽게 확인할 수 있도록 PR 메시지에도 올립니다.
https://documenter.getpostman.com/view/54815488/2sBXqQGHp8#d3222fbf-958c-491c-8fde-c5a6d953572d
Summary by CodeRabbit
Summary by CodeRabbit
새로운 기능
기타