Conversation
[FEAT]: 식당(Store) 도메인 엔티티 설계
[FEAT]: HTTPS 적용 및 NGINX Blue-Green 무중단 배포 전환 로직 수정
main 브랜치와 코드 통일
[FIX]: 불필요 도커이미지 제거 명령어 배포 스크립트에 추가
Readme 회원정보 수정
* FEAT: 사업자 인증 시 대표자 성명 입력 필드 추가 및 검증 반영 * FEAT: 가게 등록 시 사업자번호 대표 성명 입력 필드 추가 및 검증 로직 반영
📝 WalkthroughWalkthroughDocker 배포 워크플로를 개선하고, 예약 및 사용자 검증 로직에 맞춤형 메시지를 추가하며, 가게 상세 정보에 테이블 이미지 URL 및 예약 횟수를 포함하는 데이터 구조 업데이트를 수행했습니다. README 문서를 확장했습니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
.github/workflows/deploy.yml (1)
79-94:⚠️ Potential issue | 🟠 Major
docker compose up -d실패 시 자동 롤백 없음 — 서비스 다운 위험현재 배포 순서에서
docker compose down이후docker compose up -d가 실패하면(예: 이미지 호환 오류, OOM, 포트 충돌 등) 애플리케이션이 중단된 상태로 남으며 자동 복구 수단이 없습니다.
docker pull실패 →set -e로 즉시 중단, 기존 컨테이너 유지. ✅ 안전docker compose down성공 +docker compose up -d실패 → 서비스 다운, 수동 개입 필요. ❌ 위험- 다음 배포 시 line 86의 prune이 "미사용" 상태가 된 구버전 이미지를 삭제하므로 롤백 이미지마저 소실됩니다.
🛡️ 롤백 로직 추가 예시
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/eatsfine-be:latest + # 이전 이미지 태그 저장 (롤백용) + PREV_IMAGE=$(docker inspect --format='{{index .RepoDigests 0}}' ${{ secrets.DOCKERHUB_USERNAME }}/eatsfine-be:latest 2>/dev/null || echo "") + docker compose down - docker compose up -d + if ! docker compose up -d; then + echo "배포 실패 — 롤백 시도" + docker compose down + # 이전 이미지로 복구하려면 별도 태그(예: :stable) 관리 필요 + exit 1 + fi docker ps근본적인 해결책으로는
:latest외에:stable태그를 별도로 관리하거나, ECR/DockerHub의 이전 digest를 활용한 롤백 전략을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/deploy.yml around lines 79 - 94, The deploy script currently runs docker pull, then docker compose down, then docker compose up -d which can leave the service down if compose up fails; change the flow to pull the new image, attempt docker compose up -d first (without running docker compose down), and only if that up succeeds then stop/remove old containers and run docker system prune -a -f; alternatively, before tearing down capture the current image/container identifiers (via docker inspect/docker images) so you can re-tag/re-deploy the previous image on failure — update the SSH heredoc commands around docker pull, docker compose down, docker compose up -d and docker system prune -a -f to implement this conditional/rollback behavior and ensure failures from docker compose up do not leave the service down.src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java (2)
72-74:⚠️ Potential issue | 🟡 Minor
LocalDate.now()와LocalTime.now()분리 호출 — 자정 경계 불일치 가능성두 호출이 별도의 시스템 클록 읽기이므로, 자정 직전에 날짜가 바뀌면
currentDate와currentTime이 서로 다른 시각을 참조할 수 있습니다.LocalDateTime.now()로 한 번에 캡처 후 분리하는 것이 안전합니다.♻️ 수정 제안
- LocalDate currentDate = LocalDate.now(); - LocalTime currentTime = LocalTime.now(); + LocalDateTime now = LocalDateTime.now(); + LocalDate currentDate = now.toLocalDate(); + LocalTime currentTime = now.toLocalTime();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java` around lines 72 - 74, In checkFutureBookingsInLayout(TableLayout layout) avoid calling LocalDate.now() and LocalTime.now() separately; capture a single LocalDateTime now = LocalDateTime.now() and derive LocalDate currentDate = now.toLocalDate() and LocalTime currentTime = now.toLocalTime() so date/time are consistent across the method (replace the currentDate/currentTime initializations accordingly).
43-54:⚠️ Potential issue | 🟠 Major미래 예약 확인과 삭제 사이의 TOCTOU 경쟁 조건 - 락 메커니즘 적용 필수
checkFutureBookingsInLayout()에서 미래 예약이 없음을 확인한 후tableLayoutRepository.delete()를 호출하는 사이에, 다른 트랜잭션이 해당 테이블에 예약을 생성할 수 있습니다. 클래스 레벨@Transactional만으로는 이 check-and-act 시퀀스의 원자성을 보장하지 못합니다. 결과적으로 삭제 후에도 고아 예약 레코드가 남아 데이터 정합성이 깨집니다.
TableLayout또는StoreTable엔티티에 비관적 락(@Lock(LockModeType.PESSIMISTIC_WRITE)및SELECT ... FOR UPDATE) 또는 낙관적 락(@Version)을 적용하여, 체크와 삭제 사이에 다른 트랜잭션의 개입을 방지해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java` around lines 43 - 54, The check-and-delete is vulnerable to TOCTOU: before calling tableLayoutRepository.delete(existingLayout.get()) you must lock the layout (or its tables) so no concurrent booking can be inserted between checkFutureBookingsInLayout() and delete. Fix by loading the TableLayout (or its StoreTable children) with a pessimistic write lock (e.g., add a repository method like findByIdWithLock/annotate with `@Lock`(LockModeType.PESSIMISTIC_WRITE) or use EntityManager.lock(..., PESSIMISTIC_WRITE)), perform checkFutureBookingsInLayout() on that locked instance, then delete and flush; alternatively add an optimistic `@Version` field on TableLayout/StoreTable and fail the operation on concurrent updates and retry. Ensure you update or add the appropriate repository method (used in place of the current existingLayout retrieval) and keep tableLayoutRepository.delete(existingLayout.get()) and flush after acquiring the lock.src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java (1)
246-247:⚠️ Potential issue | 🟠 MajorINFO 레벨 로그에 이메일(PII) 노출
user.getEmail()을 INFO 레벨로 기록하면 로그 집계 시스템에 이메일이 평문으로 남아 개인정보 규정(GDPR/CCPA) 위반 위험이 있습니다. 추적 목적으로는userId만으로 충분합니다.🛡️ 수정 제안
- log.info("[OwnerAuth] 사장 인증 시도 - 유저ID: {}, 이메일: {}", - user.getId(), user.getEmail()); + log.info("[OwnerAuth] 사장 인증 시도 - 유저ID: {}", user.getId());As per coding guidelines, "비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지"를 점검해야 하며, 이메일 역시 사용자 식별 정보(PII)에 해당합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java` around lines 246 - 247, The INFO log in UserServiceImpl (around the Owner authentication flow) is recording user.getEmail(), exposing PII; remove the email from the info-level log and log only user.getId() (or a non-PII identifier) in the log statement used in the owner authentication method (e.g., the block that logs "[OwnerAuth] 사장 인증 시도"). If you need traceability, log a hashed or truncated identifier at DEBUG level only, but do not emit plain email at INFO.src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java (2)
109-133: 🧹 Nitpick | 🔵 Trivial
extractUpdatedFields: 리플렉션 기반 접근 방식은 취약하며, 예외를 묵인합니다.두 가지 문제가 있습니다:
- 예외 묵인: Line 127-128에서 리플렉션 실패 시 로그만 남기고 조용히 넘어갑니다.
fieldsToTrack에 오타가 있거나 DTO 필드명이 변경되면 버그가 숨겨집니다.- 접근 제어자: 이 메서드는 내부 헬퍼이므로
private이 적절합니다.Record의 컴포넌트와
fieldsToTrack리스트 간의 동기화가 보장되지 않아, DTO에 필드가 추가/삭제될 때 누락될 수 있습니다. 명시적인 null 체크 방식이 더 안전합니다.♻️ 명시적 null 체크 방식 제안
- public List<String> extractUpdatedFields(StoreReqDto.StoreUpdateDto dto) { + private List<String> extractUpdatedFields(StoreReqDto.StoreUpdateDto dto) { List<String> updated = new ArrayList<>(); - - // 검사할 필드 이름들을 리스트로 관리 - List<String> fieldsToTrack = List.of( - "storeName", "description", "phoneNumber", - "category", "depositRate", "bookingIntervalMinutes" - ); - - // 각 필드가 null이 아닌지 체크 (패턴 중복 제거) - // DTO가 Record라면 accessor 메서드를 찾아서 체크. - fieldsToTrack.forEach(fieldName -> { - try { - // Record의 필드 이름과 동일한 이름의 메서드를 호출하여 null 체크 - Object value = dto.getClass().getMethod(fieldName).invoke(dto); - if (value != null) { - updated.add(fieldName); - } - } catch (Exception e) { - log.error("필드 추출 중 에러 발생: {}", fieldName); - } - }); - + if (dto.storeName() != null) updated.add("storeName"); + if (dto.description() != null) updated.add("description"); + if (dto.phoneNumber() != null) updated.add("phoneNumber"); + if (dto.category() != null) updated.add("category"); + if (dto.depositRate() != null) updated.add("depositRate"); + if (dto.bookingIntervalMinutes() != null) updated.add("bookingIntervalMinutes"); return updated; }As per coding guidelines,
src/**: "예외가 적절히 처리되었는지 확인해줘" 및 "RuntimeException을 남발하지 않고, 의미 있는 커스텀 예외를 사용하는지 검토."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java` around lines 109 - 133, The extractUpdatedFields method currently uses reflection which swallows exceptions and hides mismatches between fieldsToTrack and the DTO; change extractUpdatedFields to private and replace the reflective loop with explicit null checks calling the DTO accessors (e.g., dto.storeName(), dto.description(), dto.phoneNumber(), dto.category(), dto.depositRate(), dto.bookingIntervalMinutes()), adding each corresponding field name to the returned list when non-null; remove the try/catch that logs and ignores reflection failures and, if any unexpected state must be signaled, throw or return a meaningful custom exception (not a generic RuntimeException) from this helper so callers can handle it.
139-158:⚠️ Potential issue | 🟠 MajorS3 업로드 후 트랜잭션 실패 시 새 파일이 고아 객체로 남을 수 있습니다.
Line 153에서 S3에 새 이미지를 업로드한 뒤, Line 154에서 DB 업데이트가 이루어지지만, 트랜잭션 커밋이 실패하면 이미 업로드된 새 S3 객체는 삭제되지 않습니다. 기존 이미지 삭제는
afterCommit으로 안전하게 처리하고 있으나 (Line 145-150), 새 이미지에 대한 롤백 처리가 없습니다.🛡️ 롤백 시 새 이미지 정리 로직 추가 제안
String key = s3Service.upload(file, "stores/" + storeId + "/main"); + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + `@Override` + public void afterCompletion(int status) { + if (status != TransactionSynchronization.STATUS_COMMITTED) { + s3Service.deleteByKey(key); + } + } + }); store.updateMainImageKey(key);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java` around lines 139 - 158, The new S3 object uploaded by s3Service.upload can be left orphaned if the DB transaction fails; fix by registering a TransactionSynchronization after obtaining the uploaded key that deletes that key on rollback: after calling s3Service.upload(...) (the local variable key) register a TransactionSynchronization (via TransactionSynchronizationManager.registerSynchronization) whose afterCompletion checks for TransactionSynchronization.STATUS_ROLLED_BACK and calls s3Service.deleteByKey(key); keep the existing afterCommit deletion of the oldKey as-is and ensure the synchronization references the new key and runs only on rollback to avoid interfering with the normal commit flow where store.updateMainImageKey(...) and toUrl(...) run.src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java (1)
78-90: 🧹 Nitpick | 🔵 Trivial
store.getTableImages()호출 시 Lazy Loading 추가 쿼리가 발생합니다.
findById로 조회한Store엔티티의tableImages컬렉션이 FetchType.LAZY로 설정되어 있어getTableImages()호출 시 추가 쿼리가 발생합니다. 리포지토리에서findByIdWithMenus(),findAllByOwner()등 다른 관계에는 fetch join을 적용하고 있으므로, 동일하게tableImages도 eager loading으로 최적화하거나menus처럼@BatchSize어노테이션을 추가해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java` around lines 78 - 90, getStoreDetail triggers a lazy-load query when calling store.getTableImages(); fix by ensuring tableImages are fetched with the initial query or batched: either add a repository method (e.g., findByIdWithTableImages or extend existing findByIdWithMenus) that uses a JPQL/Querydsl fetch join for the Store.tableImages and use that method in getStoreDetail, or annotate the Store.tableImages collection with `@BatchSize` to avoid N+1s; update getStoreDetail to call the new repository method (still use s3Service.toUrl and StoreConverter.toDetailDto) so no extra query occurs during mapping.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Around line 77-80: There are several Markdown lint violations: add a blank
line immediately before each closing </details> tag where flagged (ensure an
empty line above the </details> at the occurrences shown), remove the single
trailing space on the flagged line (eliminate the trailing whitespace at line
with MD009), and convert the closed ATX heading "## 🛠️ 팀원 정보 ##" to an open ATX
heading by removing the trailing " ##" so it reads "## 🛠️ 팀원 정보"; also scan the
file for any other trailing spaces or missing blank lines after code blocks and
fix them similarly.
- Line 114: Update the commit example string shown as git commit -m "feat: 자세한
내용 적기" to match the document's canonical commit format by changing it to use
brackets and uppercase, e.g. git commit -m "[FEAT]: 자세한 내용 적기"; ensure this
example aligns with the earlier commit-type table and other examples (the
"[FEAT]:" style) so all examples are consistent.
In
`@src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java`:
- Line 39: The tableIds field in BookingRequestDTO is annotated with `@NotNull`
but can still be an empty list; update the BookingRequestDTO class to add the
`@NotEmpty` annotation to the List<Long> tableIds field (matching how menuItems is
validated) so empty lists are rejected during validation, ensuring the service
never receives a booking request without table IDs.
In
`@src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java`:
- Around line 13-17: BusinessNumberReqDto now includes a required "name" field
(the record field name with `@NotBlank` and `@Size` annotations), which makes the
request body validation reject clients that omit it; update the external API
contract and consumer-facing docs (OpenAPI/Swagger, API reference, changelog) to
declare "name" as a required property, run/invalidate any generated client SDKs,
and add or update integration tests that exercise the endpoint deserialization
to ensure existing clients are identified and migrated — if backward
compatibility is required instead, remove or relax the `@NotBlank/`@Size on the
name field in BusinessNumberReqDto and add a migration plan in the docs.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java`:
- Around line 35-38: The code in StoreConverter.java uses
store.getBusinessHours().stream().filter(bh -> !bh.isClosed()).findFirst() to
populate breakStartTime/breakEndTime, which makes the returned break times
non-deterministic when multiple open days have different breaks; update the
conversion logic in StoreConverter (the BusinessHours handling) to either (a)
enforce and assert that all non-closed BusinessHours have identical
breakStartTime/breakEndTime before selecting a value, or (b) aggregate/choose a
deterministic value (e.g., earliest breakStart/latest breakEnd or explicit
weekday mapping) and populate StoreDetailDto accordingly; if the DTO is intended
to hold only a single break window, add a clear comment or validation in
StoreConverter to document/enforce this invariant.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java`:
- Line 60: The current info log in StoreCommandServiceImpl prints the full
business number (dto.businessNumberDto().businessNumber()), which is sensitive;
change this to avoid logging the raw value by either (a) lowering the log level
to DEBUG and keeping the message but not exposing production logs, or (b) mask
the business number before logging (e.g., replace all but the last N characters
with asterisks) and then log the masked value. Update the log statement in
StoreCommandServiceImpl to use the chosen approach and ensure any tests or
callers referencing this log are adjusted accordingly.
- Around line 55-58: StoreCreateDto의 businessNumberDto가 `@Valid만` 사용되어 null이 들어올 때
NullPointerException이 발생하므로 해당 필드에 `@NotNull`(message = "사업자 정보는 필수입니다.") 애노테이션을
추가하세요; 구체적으로 StoreCreateDto(또는 메서드 파라미터 선언)에서 businessNumberDto 타입
BusinessNumberReqDto.BusinessNumberDto에 `@Valid와` 함께 `@NotNull을` 붙여 null 입력을 차단하고
businessNumberValidator.validate(...) 호출 시 NPE를 방지합니다.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java`:
- Around line 115-116: In StoreQueryServiceImpl, replace the single-line
conditional statements that omit braces (e.g., "if (openToday) return true;")
with braced blocks to prevent future maintenance errors; locate both occurrences
referenced (the if using the openToday variable and the similar if at the later
occurrence around lines 125–126) and change them to use full block form (add {
... } around the return or subsequent statement) so each conditional is explicit
and safe to extend.
- Around line 84-88: Replace the fully-qualified class name used in the stream
comparator with an import to improve readability: add an import for
com.eatsfine.eatsfine.domain.tableimage.entity.TableImage at the top of
StoreQueryServiceImpl and change the comparator reference from
com.eatsfine.eatsfine.domain.tableimage.entity.TableImage::getImageOrder to
TableImage::getImageOrder in the stream that builds tableImageUrls (the pipeline
starting from store.getTableImages() and calling s3Service.toUrl).
- Around line 165-175: Replace the raw Object[] projection in
StoreQueryServiceImpl (after storeRepository.findAllByOwner) by defining a
BookingCountProjection interface with Long getStoreId() and Long getCount(),
change bookingRepository.countActiveBookingsByStores to return
List<BookingCountProjection>, then build bookingCountMap by streaming the
projection list and collecting toMap(BookingCountProjection::getStoreId,
BookingCountProjection::getCount) so you avoid casts and gain compile-time type
safety.
In
`@src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java`:
- Around line 16-17: JoinDto.name is missing the same `@Size`(min = 2, max = 20)
constraint used in VerifyOwnerDto.name, causing users to register names that
later fail owner verification; add the `@Size`(min = 2, max = 20) annotation to
the name field in JoinDto (alongside the existing `@NotBlank`) and provide a
consistent validation message (e.g., "이름은 2자 이상 20자 이하여야 합니다.") so both DTOs
enforce identical name length rules.
---
Outside diff comments:
In @.github/workflows/deploy.yml:
- Around line 79-94: The deploy script currently runs docker pull, then docker
compose down, then docker compose up -d which can leave the service down if
compose up fails; change the flow to pull the new image, attempt docker compose
up -d first (without running docker compose down), and only if that up succeeds
then stop/remove old containers and run docker system prune -a -f;
alternatively, before tearing down capture the current image/container
identifiers (via docker inspect/docker images) so you can re-tag/re-deploy the
previous image on failure — update the SSH heredoc commands around docker pull,
docker compose down, docker compose up -d and docker system prune -a -f to
implement this conditional/rollback behavior and ensure failures from docker
compose up do not leave the service down.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java`:
- Around line 109-133: The extractUpdatedFields method currently uses reflection
which swallows exceptions and hides mismatches between fieldsToTrack and the
DTO; change extractUpdatedFields to private and replace the reflective loop with
explicit null checks calling the DTO accessors (e.g., dto.storeName(),
dto.description(), dto.phoneNumber(), dto.category(), dto.depositRate(),
dto.bookingIntervalMinutes()), adding each corresponding field name to the
returned list when non-null; remove the try/catch that logs and ignores
reflection failures and, if any unexpected state must be signaled, throw or
return a meaningful custom exception (not a generic RuntimeException) from this
helper so callers can handle it.
- Around line 139-158: The new S3 object uploaded by s3Service.upload can be
left orphaned if the DB transaction fails; fix by registering a
TransactionSynchronization after obtaining the uploaded key that deletes that
key on rollback: after calling s3Service.upload(...) (the local variable key)
register a TransactionSynchronization (via
TransactionSynchronizationManager.registerSynchronization) whose afterCompletion
checks for TransactionSynchronization.STATUS_ROLLED_BACK and calls
s3Service.deleteByKey(key); keep the existing afterCommit deletion of the oldKey
as-is and ensure the synchronization references the new key and runs only on
rollback to avoid interfering with the normal commit flow where
store.updateMainImageKey(...) and toUrl(...) run.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java`:
- Around line 78-90: getStoreDetail triggers a lazy-load query when calling
store.getTableImages(); fix by ensuring tableImages are fetched with the initial
query or batched: either add a repository method (e.g., findByIdWithTableImages
or extend existing findByIdWithMenus) that uses a JPQL/Querydsl fetch join for
the Store.tableImages and use that method in getStoreDetail, or annotate the
Store.tableImages collection with `@BatchSize` to avoid N+1s; update
getStoreDetail to call the new repository method (still use s3Service.toUrl and
StoreConverter.toDetailDto) so no extra query occurs during mapping.
In
`@src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java`:
- Around line 72-74: In checkFutureBookingsInLayout(TableLayout layout) avoid
calling LocalDate.now() and LocalTime.now() separately; capture a single
LocalDateTime now = LocalDateTime.now() and derive LocalDate currentDate =
now.toLocalDate() and LocalTime currentTime = now.toLocalTime() so date/time are
consistent across the method (replace the currentDate/currentTime
initializations accordingly).
- Around line 43-54: The check-and-delete is vulnerable to TOCTOU: before
calling tableLayoutRepository.delete(existingLayout.get()) you must lock the
layout (or its tables) so no concurrent booking can be inserted between
checkFutureBookingsInLayout() and delete. Fix by loading the TableLayout (or its
StoreTable children) with a pessimistic write lock (e.g., add a repository
method like findByIdWithLock/annotate with `@Lock`(LockModeType.PESSIMISTIC_WRITE)
or use EntityManager.lock(..., PESSIMISTIC_WRITE)), perform
checkFutureBookingsInLayout() on that locked instance, then delete and flush;
alternatively add an optimistic `@Version` field on TableLayout/StoreTable and
fail the operation on concurrent updates and retry. Ensure you update or add the
appropriate repository method (used in place of the current existingLayout
retrieval) and keep tableLayoutRepository.delete(existingLayout.get()) and flush
after acquiring the lock.
In
`@src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java`:
- Around line 246-247: The INFO log in UserServiceImpl (around the Owner
authentication flow) is recording user.getEmail(), exposing PII; remove the
email from the info-level log and log only user.getId() (or a non-PII
identifier) in the log statement used in the owner authentication method (e.g.,
the block that logs "[OwnerAuth] 사장 인증 시도"). If you need traceability, log a
hashed or truncated identifier at DEBUG level only, but do not emit plain email
at INFO.
| ``` | ||
| </details> | ||
|
|
||
|
|
There was a problem hiding this comment.
마크다운 린트 경고 수정 필요
정적 분석 도구가 다음 4개의 위반을 감지했습니다:
| 줄 | 규칙 | 내용 |
|---|---|---|
| 77 | MD031 | 코드 블록 뒤에 빈 줄 필요 (</details> 앞) |
| 80 | MD009 | 후행 공백 1개 (0 또는 2개여야 함) |
| 144 | MD003 | ATX 닫힘 스타일 (## 🛠️ 팀원 정보 ##) → ATX 열림 스타일 사용 필요 |
| 245 | MD031 | 코드 블록 뒤에 빈 줄 필요 (</details> 앞) |
✏️ 수정 제안
-## 🛠️ 팀원 정보 ##
+## 🛠️ 팀원 정보Lines 77 및 245 — </details> 직전에 빈 줄 추가:
Also applies to: 144-144, 245-245
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 77-77: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
[warning] 80-80: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 77 - 80, There are several Markdown lint violations:
add a blank line immediately before each closing </details> tag where flagged
(ensure an empty line above the </details> at the occurrences shown), remove the
single trailing space on the flagged line (eliminate the trailing whitespace at
line with MD009), and convert the closed ATX heading "## 🛠️ 팀원 정보 ##" to an
open ATX heading by removing the trailing " ##" so it reads "## 🛠️ 팀원 정보"; also
scan the file for any other trailing spaces or missing blank lines after code
blocks and fix them similarly.
|
|
||
| ```bash | ||
| git add . # 필요하면 git add file명 으로 특정 파일만 추가해도 됨 | ||
| git commit -m "feat: 자세한 내용 적기" |
There was a problem hiding this comment.
커밋 예시 형식이 문서 내 규칙과 불일치
이 줄의 예시는 "feat: 자세한 내용 적기"이지만, 같은 문서(Lines 38–40)의 예시와 커밋 타입 테이블(Line 25–33)은 [FEAT]: 형식(대괄호, 대문자)을 사용합니다. 실제 규칙에 맞게 수정이 필요합니다.
✏️ 수정 제안
-git commit -m "feat: 자세한 내용 적기"
+git commit -m "[FEAT]: 자세한 내용 적기"📝 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.
| git commit -m "feat: 자세한 내용 적기" | |
| git commit -m "[FEAT]: 자세한 내용 적기" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` at line 114, Update the commit example string shown as git commit
-m "feat: 자세한 내용 적기" to match the document's canonical commit format by changing
it to use brackets and uppercase, e.g. git commit -m "[FEAT]: 자세한 내용 적기"; ensure
this example aligns with the earlier commit-type table and other examples (the
"[FEAT]:" style) so all examples are consistent.
| @@ -36,12 +38,12 @@ public record CreateBookingDTO( | |||
| @NotNull @Min(1) Integer partySize, | |||
| @NotNull List<Long> tableIds, | |||
There was a problem hiding this comment.
tableIds에 @NotEmpty 누락 — 빈 리스트 통과 가능
@NotNull은 빈 리스트([])를 허용합니다. 예약 생성 시 테이블이 없는 요청이 서비스 레이어까지 도달해 런타임 예외나 의도치 않은 동작을 유발할 수 있습니다. menuItems와 동일하게 @NotEmpty를 적용하는 것을 권장합니다.
✏️ 수정 제안
- `@NotNull` List<Long> tableIds,
+ `@NotEmpty`(message = "테이블을 하나 이상 선택해야 합니다.") List<Long> tableIds,📝 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.
| @NotNull List<Long> tableIds, | |
| `@NotEmpty`(message = "테이블을 하나 이상 선택해야 합니다.") List<Long> tableIds, |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java`
at line 39, The tableIds field in BookingRequestDTO is annotated with `@NotNull`
but can still be an empty list; update the BookingRequestDTO class to add the
`@NotEmpty` annotation to the List<Long> tableIds field (matching how menuItems is
validated) so empty lists are rejected during validation, ensuring the service
never receives a booking request without table IDs.
|
|
||
| @Schema(description = "이름", example = "홍길동") | ||
| @NotBlank(message = "이름은 필수입니다.") | ||
| @Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.") | ||
| String name, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "BusinessNumberDto" --type java -A 3 -B 1Repository: Eatsfine/BE
Length of output: 3340
🏁 Script executed:
cat -n src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.javaRepository: Eatsfine/BE
Length of output: 1123
🏁 Script executed:
# Search for test files that may construct BusinessNumberDto
find . -type f -name "*Test*.java" -o -name "*Tests.java" | xargs grep -l "BusinessNumberDto" 2>/dev/null
# Also search for builder() calls on BusinessNumberDto
rg -n "BusinessNumberDto\.builder\(\)" --type java -A 3 -B 1Repository: Eatsfine/BE
Length of output: 37
🏁 Script executed:
# Search for direct constructor calls with "new"
rg -n "new BusinessNumberDto" --type java
# Also check for any other patterns of instantiation
rg -n "BusinessNumberDto(" --type java | head -30Repository: Eatsfine/BE
Length of output: 141
🏁 Script executed:
# Search with escaped parenthesis
rg -n "BusinessNumberDto\(" --type java -A 2 -B 1 | grep -v "BusinessNumberResDto"Repository: Eatsfine/BE
Length of output: 537
🏁 Script executed:
# Search for test files that might construct StoreReqDto or BusinessNumberDto
rg -n "StoreReqDto\|BusinessNumberDto" --type java test/ 2>/dev/null || rg -n "StoreReqDto" --type java | grep -i testRepository: Eatsfine/BE
Length of output: 426
🏁 Script executed:
# Find test directory structure
find . -type d -name "test" -o -type d -name "tests" 2>/dev/null
# List Java test files
fd ".*Test\.java$|.*Tests\.java$" . --type f 2>/dev/null | head -10Repository: Eatsfine/BE
Length of output: 299
🏁 Script executed:
# Search for BusinessNumberDto in test files
grep -r "BusinessNumberDto" src/test/ 2>/dev/null
# Also search for StoreReqDto in test files
grep -r "StoreReqDto\|StoreCreateDto" src/test/ 2>/dev/nullRepository: Eatsfine/BE
Length of output: 37
API 계약 변경 — 외부 호출자는 "name" 필드를 포함해야 함
BusinessNumberDto 레코드에 name 필드가 추가되었습니다. 이 DTO는 Spring의 요청 본문 역직렬화를 통해 사용되므로, 기존 API 클라이언트가 name 필드 없이 요청을 보내면 @NotBlank 검증 실패로 400 Bad Request가 발생합니다. 외부 API 문서를 업데이트하고 기존 클라이언트 영향도를 검토하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java`
around lines 13 - 17, BusinessNumberReqDto now includes a required "name" field
(the record field name with `@NotBlank` and `@Size` annotations), which makes the
request body validation reject clients that omit it; update the external API
contract and consumer-facing docs (OpenAPI/Swagger, API reference, changelog) to
declare "name" as a required property, run/invalidate any generated client SDKs,
and add or update integration tests that exercise the endpoint deserialization
to ensure existing clients are identified and migrated — if backward
compatibility is required instead, remove or relax the `@NotBlank/`@Size on the
name field in BusinessNumberReqDto and add a migration plan in the docs.
| BusinessHours anyOpenDay = store.getBusinessHours().stream() | ||
| .filter(bh -> !bh.isClosed()) | ||
| .findFirst() | ||
| .orElse(null); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
findFirst()로 취득한 첫 번째 영업일의 휴게시간만 반환
복수 영업일의 휴게시간이 다를 경우, 반환되는 breakStartTime/breakEndTime이 비결정적(순서에 따라 다른 요일의 값)이 될 수 있습니다. 현재 StoreDetailDto가 단일 휴게시간만 포함하는 설계라면 이 점을 주석으로 명시하거나, 모든 영업일의 휴게시간이 동일함을 보장하는 제약이 필요합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java`
around lines 35 - 38, The code in StoreConverter.java uses
store.getBusinessHours().stream().filter(bh -> !bh.isClosed()).findFirst() to
populate breakStartTime/breakEndTime, which makes the returned break times
non-deterministic when multiple open days have different breaks; update the
conversion logic in StoreConverter (the BusinessHours handling) to either (a)
enforce and assert that all non-closed BusinessHours have identical
breakStartTime/breakEndTime before selecting a value, or (b) aggregate/choose a
deterministic value (e.g., earliest breakStart/latest breakEnd or explicit
weekday mapping) and populate StoreDetailDto accordingly; if the DTO is intended
to hold only a single break window, add a clear comment or validation in
StoreConverter to document/enforce this invariant.
| user.getName()); | ||
| dto.businessNumberDto().name()); | ||
|
|
||
| log.info("사업자 번호 검증 성공: {}", dto.businessNumberDto().businessNumber()); |
There was a problem hiding this comment.
사업자 번호를 로그에 출력하고 있습니다.
사업자 등록번호는 민감한 사업 정보에 해당할 수 있습니다. 운영 환경에서는 마스킹 처리하거나 로그 레벨을 DEBUG로 변경하는 것을 권장합니다.
🛡️ 로그 레벨 변경 제안
- log.info("사업자 번호 검증 성공: {}", dto.businessNumberDto().businessNumber());
+ log.debug("사업자 번호 검증 성공");As per coding guidelines, src/**: "비밀번호, 토큰 등 민감한 정보가 로깅되지 않는지."
📝 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.
| log.info("사업자 번호 검증 성공: {}", dto.businessNumberDto().businessNumber()); | |
| log.debug("사업자 번호 검증 성공"); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java`
at line 60, The current info log in StoreCommandServiceImpl prints the full
business number (dto.businessNumberDto().businessNumber()), which is sensitive;
change this to avoid logging the raw value by either (a) lowering the log level
to DEBUG and keeping the message but not exposing production logs, or (b) mask
the business number before logging (e.g., replace all but the last N characters
with asterisks) and then log the masked value. Update the log statement in
StoreCommandServiceImpl to use the chosen approach and ensure any tests or
callers referencing this log are adjusted accordingly.
| List<String> tableImageUrls = store.getTableImages().stream() | ||
| .sorted(java.util.Comparator | ||
| .comparingInt(com.eatsfine.eatsfine.domain.tableimage.entity.TableImage::getImageOrder)) | ||
| .map(ti -> s3Service.toUrl(ti.getTableImageKey())) | ||
| .toList(); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
정규화된 클래스명(FQCN) 대신 import를 사용하세요.
com.eatsfine.eatsfine.domain.tableimage.entity.TableImage::getImageOrder를 인라인으로 사용하고 있어 가독성이 떨어집니다. TableImage를 import하여 간결하게 작성하세요.
♻️ import 추가 및 간결화 제안
파일 상단에 import 추가:
import com.eatsfine.eatsfine.domain.tableimage.entity.TableImage; List<String> tableImageUrls = store.getTableImages().stream()
- .sorted(java.util.Comparator
- .comparingInt(com.eatsfine.eatsfine.domain.tableimage.entity.TableImage::getImageOrder))
+ .sorted(Comparator.comparingInt(TableImage::getImageOrder))
.map(ti -> s3Service.toUrl(ti.getTableImageKey()))
.toList();As per coding guidelines, src/**: "의미 있는 변수명과 메소드명을 사용했는지" — 코드 품질 & 가독성 항목에 해당합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java`
around lines 84 - 88, Replace the fully-qualified class name used in the stream
comparator with an import to improve readability: add an import for
com.eatsfine.eatsfine.domain.tableimage.entity.TableImage at the top of
StoreQueryServiceImpl and change the comparator reference from
com.eatsfine.eatsfine.domain.tableimage.entity.TableImage::getImageOrder to
TableImage::getImageOrder in the stream that builds tableImageUrls (the pipeline
starting from store.getTableImages() and calling s3Service.toUrl).
| if (openToday) | ||
| return true; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
중괄호 없는 if 문은 유지보수 시 버그를 유발할 수 있습니다.
단일 문장 if에 중괄호를 생략하면, 이후 코드 추가 시 실수로 조건 밖에 작성하는 버그가 발생할 수 있습니다.
♻️ 중괄호 추가 제안
- if (openToday)
- return true;
+ if (openToday) {
+ return true;
+ }- if (bh.isClosed())
- return false;
+ if (bh.isClosed()) {
+ return false;
+ }Also applies to: 125-126
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java`
around lines 115 - 116, In StoreQueryServiceImpl, replace the single-line
conditional statements that omit braces (e.g., "if (openToday) return true;")
with braced blocks to prevent future maintenance errors; locate both occurrences
referenced (the if using the openToday variable and the similar if at the later
occurrence around lines 125–126) and change them to use full block form (add {
... } around the return or subsequent statement) so each conditional is explicit
and safe to extend.
| List<Store> myStores = storeRepository.findAllByOwner(user); | ||
|
|
||
| if(myStores.isEmpty()) { | ||
| if (myStores.isEmpty()) { | ||
| return StoreConverter.toMyStoreListDto(List.of()); | ||
| } | ||
| // N+1 문제 해결을 위한 Bulk Query 실행 | ||
| List<Object[]> bookingCounts = bookingRepository.countActiveBookingsByStores(myStores); | ||
| Map<Long, Long> bookingCountMap = bookingCounts.stream() | ||
| .collect(Collectors.toMap( | ||
| row -> (Long) row[0], | ||
| row -> (Long) row[1] | ||
| )); | ||
| row -> (Long) row[1])); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
fd "BookingRepository.java" -t f --exec cat {}Repository: Eatsfine/BE
Length of output: 6474
🏁 Script executed:
# Search for DTO/Projection classes related to booking
fd -e java -t f | xargs grep -l "Projection\|CountDto\|BookingCount" | head -20Repository: Eatsfine/BE
Length of output: 372
🏁 Script executed:
# Also check if there are any record classes or DTOs in booking domain
find . -path "*/booking/*" -name "*.java" -type f | xargs grep -l "record \|class.*Dto" | head -20Repository: Eatsfine/BE
Length of output: 232
🏁 Script executed:
# Check StoreConverter to understand current conversion logic
cat src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.javaRepository: Eatsfine/BE
Length of output: 5430
🏁 Script executed:
# Search for interface-based projections (Spring Data pattern)
rg "interface.*Projection\|@QueryProjection" -t javaRepository: Eatsfine/BE
Length of output: 37
🏁 Script executed:
# Check the complete StoreQueryServiceImpl to see the full context of Object[] usage
cat src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java | head -200Repository: Eatsfine/BE
Length of output: 7676
Object[] 캐스팅 대신 프로젝션 인터페이스 사용으로 타입 안전성 개선
N+1 문제를 해결하기 위한 Bulk Query 패턴은 좋습니다. 다만 Object[] 타입 캐스팅은 타입 안전성이 부족합니다. 코드베이스에서 이미 사용 중인 프로젝션 패턴(StoreSearchResult)을 따라 인터페이스 기반 프로젝션을 적용하면 컴파일 타임에 타입 오류를 잡을 수 있습니다.
예를 들어 다음과 같이 프로젝션 인터페이스를 정의하면:
public interface BookingCountProjection {
Long getStoreId();
Long getCount();
}Repository 메서드를 개선할 수 있습니다:
List<BookingCountProjection> countActiveBookingsByStores(`@Param`("stores") List<Store> stores);그러면 Object[] 캐스팅 없이 타입 안전하게 처리 가능합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java`
around lines 165 - 175, Replace the raw Object[] projection in
StoreQueryServiceImpl (after storeRepository.findAllByOwner) by defining a
BookingCountProjection interface with Long getStoreId() and Long getCount(),
change bookingRepository.countActiveBookingsByStores to return
List<BookingCountProjection>, then build bookingCountMap by streaming the
projection list and collecting toMap(BookingCountProjection::getStoreId,
BookingCountProjection::getCount) so you avoid casts and gain compile-time type
safety.
| @NotBlank(message = "이름은 필수입니다.") | ||
| private String name; // 이름 | ||
| private String name; // 이름 |
There was a problem hiding this comment.
JoinDto.name에 @Size 제약 누락 — VerifyOwnerDto.name과 불일치
VerifyOwnerDto.name에는 @Size(min = 2, max = 20)이 추가되었지만, JoinDto.name에는 해당 제약이 없습니다. 회원가입 시 1자 또는 21자 이상의 이름으로 가입한 사용자는 나중에 사장 인증 단계에서 같은 이름으로 검증 실패를 겪게 됩니다.
✏️ 수정 제안
`@NotBlank`(message = "이름은 필수입니다.")
+ `@Size`(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
private String name;📝 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.
| @NotBlank(message = "이름은 필수입니다.") | |
| private String name; // 이름 | |
| private String name; // 이름 | |
| `@NotBlank`(message = "이름은 필수입니다.") | |
| `@Size`(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.") | |
| private String name; // 이름 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java`
around lines 16 - 17, JoinDto.name is missing the same `@Size`(min = 2, max = 20)
constraint used in VerifyOwnerDto.name, causing users to register names that
later fail owner verification; add the `@Size`(min = 2, max = 20) annotation to
the name field in JoinDto (alongside the existing `@NotBlank`) and provide a
consistent validation message (e.g., "이름은 2자 이상 20자 이하여야 합니다.") so both DTOs
enforce identical name length rules.
💡 작업 개요
✅ 작업 내용
🧪 테스트 내용
📝 기타 참고 사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
버그 수정
문서
Chores