Skip to content

[CHORE]: develop 브랜치 최신 변경사항 main 반영 #145

Merged
twodo0 merged 297 commits intomainfrom
develop
Feb 19, 2026
Merged

[CHORE]: develop 브랜치 최신 변경사항 main 반영 #145
twodo0 merged 297 commits intomainfrom
develop

Conversation

@twodo0
Copy link
Contributor

@twodo0 twodo0 commented Feb 19, 2026

💡 작업 개요

  • 최신 변경사항 main 반영

✅ 작업 내용

  • 기능 개발
  • 버그 수정
  • 리팩토링
  • 주석/포맷 정리
  • 기타 설정

🧪 테스트 내용

  • 로컬 정상 빌드 확인 완료

📝 기타 참고 사항

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 식당 상세 정보에 테이블 이미지 추가
    • 가게 소유자를 위한 예약 건수 추적 기능 추가
  • 버그 수정

    • 예약 시 메뉴 선택 및 수량에 대한 유효성 검사 강화
    • 사업자 등록 및 소유자 인증 시 필수 이름 필드 추가
  • 문서

    • README 프로젝트 개요, 설정 지침 및 워크플로우 정보 확대
  • Chores

    • Docker 배포 최적화

twodo0 and others added 30 commits January 8, 2026 16:29
[FEAT]: 식당(Store) 도메인 엔티티 설계
[FEAT]: HTTPS 적용 및 NGINX Blue-Green 무중단 배포 전환 로직 수정
@twodo0 twodo0 self-assigned this Feb 19, 2026
@twodo0 twodo0 added the chore label Feb 19, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Docker 배포 워크플로를 개선하고, 예약 및 사용자 검증 로직에 맞춤형 메시지를 추가하며, 가게 상세 정보에 테이블 이미지 URL 및 예약 횟수를 포함하는 데이터 구조 업데이트를 수행했습니다. README 문서를 확장했습니다.

Changes

Cohort / File(s) Summary
Docker 배포 워크플로
.github/workflows/deploy.yml
EC2 배포 전 docker system prune -a -f를 추가하여 Docker 리소스를 적극적으로 정리하고, 기존 이미지 정리 단계 제거
프로젝트 문서
README.md
프로젝트 개요, 커밋 컨벤션, 폴더 구조, 브랜치 전략, 워크플로, 보안 규칙, 팀 규칙, 설정 및 실행 지침으로 문서 확대
예약 DTO 검증
src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java
CreateBookingDTO의 menuItems에 @NotEmpty 추가 및 맞춤형 메시지 부여, MenuOrderDto의 quantity에 최소값 검증 메시지 추가
사업자 번호 DTO
src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java
BusinessNumberDto 레코드에 name 필드 추가 (NotBlank, Size 검증 포함)
가게 상세 조회 서비스
src/main/java/com/eatsfine/eatsfine/domain/store/converter/StoreConverter.java, src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java
StoreConverter.toDetailDto 메서드에 List<String> tableImageUrls 파라미터 추가하여 테이블 이미지 URL 전달
가게 등록 및 내 가게 조회
src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java, src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreQueryServiceImpl.java
사업자 검증에 DTO의 name 값 사용으로 변경, 내 가게 조회에 예약 횟수 포함, StoreConverter.toMyStoreDtototalBookingCount 파라미터 추가
테이블 레이아웃 관리
src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java
기존 활성 레이아웃 비활성화 대신 삭제 로직으로 변경 (향후 예약이 없을 때)
사용자 DTO 및 검증
src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java, src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java
VerifyOwnerDto에 name 필드 추가 (검증 포함), 사업자 검증에 DTO의 name 값 사용
코드 정리
src/main/java/com/eatsfine/eatsfine/domain/user/status/UserErrorStatus.java, src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java
빈 줄 제거 및 주석 정리 (기능 변화 없음)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • CokaNuri
  • sonjunkyu
  • zerochani
  • SungMinju

Poem

🐰 검증 메시지에 한글을 넣고,
테이블 이미지도 쏙쏙 담아,
예약 횟수를 세어보니,
가게 정보는 더욱 풍성하네!
Docker도 깨끗이 정리하고,
README도 한껏 펼쳤으니,
완벽한 업데이트 완성! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive PR 설명은 필수 템플릿의 주요 섹션을 포함하고 있으나, 작업 내용 체크리스트가 모두 미체크 상태이고 구체적인 변경사항이 불명확합니다. 작업 내용 중 실제로 포함된 항목(기능 개발, 리팩토링, 주석/포맷 정리 등)을 명확히 체크하고, 주요 변경사항을 구체적으로 설명해주세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 develop 브랜치의 최신 변경사항을 main 반영한다는 PR의 핵심 목적을 명확하게 설명하고 있으며, 변경사항 요약과 일치합니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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.

@twodo0 twodo0 merged commit 319d107 into main Feb 19, 2026
1 of 2 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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() 분리 호출 — 자정 경계 불일치 가능성

두 호출이 별도의 시스템 클록 읽기이므로, 자정 직전에 날짜가 바뀌면 currentDatecurrentTime이 서로 다른 시각을 참조할 수 있습니다. 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 | 🟠 Major

INFO 레벨 로그에 이메일(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: 리플렉션 기반 접근 방식은 취약하며, 예외를 묵인합니다.

두 가지 문제가 있습니다:

  1. 예외 묵인: Line 127-128에서 리플렉션 실패 시 로그만 남기고 조용히 넘어갑니다. fieldsToTrack에 오타가 있거나 DTO 필드명이 변경되면 버그가 숨겨집니다.
  2. 접근 제어자: 이 메서드는 내부 헬퍼이므로 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 | 🟠 Major

S3 업로드 후 트랜잭션 실패 시 새 파일이 고아 객체로 남을 수 있습니다.

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.

Comment on lines +77 to +80
```
</details>


Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

마크다운 린트 경고 수정 필요

정적 분석 도구가 다음 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: 자세한 내용 적기"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

커밋 예시 형식이 문서 내 규칙과 불일치

이 줄의 예시는 "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.

Suggested change
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,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
@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.

Comment on lines +13 to +17

@Schema(description = "이름", example = "홍길동")
@NotBlank(message = "이름은 필수입니다.")
@Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
String name,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "BusinessNumberDto" --type java -A 3 -B 1

Repository: Eatsfine/BE

Length of output: 3340


🏁 Script executed:

cat -n src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java

Repository: 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 1

Repository: 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 -30

Repository: 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 test

Repository: 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 -10

Repository: 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/null

Repository: 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.

Comment on lines +35 to +38
BusinessHours anyOpenDay = store.getBusinessHours().stream()
.filter(bh -> !bh.isClosed())
.findFirst()
.orElse(null);
Copy link

Choose a reason for hiding this comment

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

🧹 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());
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사업자 번호를 로그에 출력하고 있습니다.

사업자 등록번호는 민감한 사업 정보에 해당할 수 있습니다. 운영 환경에서는 마스킹 처리하거나 로그 레벨을 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.

Suggested change
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.

Comment on lines +84 to +88
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();
Copy link

Choose a reason for hiding this comment

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

🛠️ 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).

Comment on lines +115 to +116
if (openToday)
return true;
Copy link

Choose a reason for hiding this comment

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

🧹 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.

Comment on lines 165 to +175
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]));
Copy link

Choose a reason for hiding this comment

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

🧹 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 -20

Repository: 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 -20

Repository: 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.java

Repository: Eatsfine/BE

Length of output: 5430


🏁 Script executed:

# Search for interface-based projections (Spring Data pattern)
rg "interface.*Projection\|@QueryProjection" -t java

Repository: 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 -200

Repository: 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.

Comment on lines 16 to +17
@NotBlank(message = "이름은 필수입니다.")
private String name; // 이름
private String name; // 이름
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants