Skip to content

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

Merged
twodo0 merged 296 commits intomainfrom
develop
Feb 18, 2026
Merged

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

Conversation

@twodo0
Copy link
Contributor

@twodo0 twodo0 commented Feb 18, 2026

💡 작업 개요

  • 사업자번호 인증 시 이름을 별도로 입력받도록 수정 후 재배포

✅ 작업 내용

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

🧪 테스트 내용

📝 기타 참고 사항

Summary by CodeRabbit

Release Notes

  • Documentation

    • Added comprehensive project documentation including setup instructions, API reference, tech stack details, and contribution guidelines.
  • Bug Fixes

    • Enhanced validation error messages for booking requests and owner verification processes to improve user feedback clarity.
  • Improvements

    • Added name field validation requirement for business owner registration.
    • Optimized deployment cleanup workflow for better efficiency.

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

coderabbitai bot commented Feb 18, 2026

📝 Walkthrough

Walkthrough

이 PR은 DTO 검증 강화, 비즈니스 번호 검증 개선, 테이블 레이아웃 생성 로직 변경, Docker 배포 최적화, 그리고 README 문서화를 포함합니다. 대부분의 변경은 검증 규칙 추가 및 마이너 리팩토링입니다.

Changes

Cohort / File(s) Summary
설정 및 문서화
.github/workflows/deploy.yml, README.md
Docker 컨테이너/이미지 전체 정리 단계 추가 및 README 다중 섹션(API 문서, 기술 스택, Git 규칙, 폴더 구조, 시작 가이드 등)으로 전환
예약 요청 DTO 검증 강화
src/main/java/.../domain/booking/dto/request/BookingRequestDTO.java
menuItems 필드에 @Valid @NotEmpty 추가, quantity 필드에 커스텀 메시지 포함 @Min 적용
비즈니스 번호 및 저장소 검증
src/main/java/.../domain/businessnumber/dto/BusinessNumberReqDto.java, src/main/java/.../domain/store/service/StoreCommandServiceImpl.java
BusinessNumberDto에 name 필드 추가(검증 포함), createStore에서 검증 시 name 소스를 user에서 dto로 변경
사용자 검증 DTO 및 서비스
src/main/java/.../domain/user/dto/request/UserRequestDto.java, src/main/java/.../domain/user/service/userService/UserServiceImpl.java
VerifyOwnerDto에 검증된 name 필드 추가, 비밀번호 정규식 포맷 정리, verifyOwner에서 검증 시 name 소스를 user에서 dto로 변경
테이블 레이아웃 로직 개선
src/main/java/.../domain/table_layout/service/TableLayoutCommandServiceImpl.java
createLayout에서 기존 레이아웃 처리 방식을 비활성화 후 재생성에서 삭제 후 flush로 변경
코드 정리
src/main/java/.../domain/user/status/UserErrorStatus.java, src/main/java/.../global/auth/AuthCookieProvider.java
불필요한 공백 제거, 인라인 주석 제거

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~18 minutes

Possibly related PRs

Suggested labels

refactor

Suggested reviewers

  • CokaNuri
  • sonjunkyu
  • zerochani
  • SungMinju

Poem

🐰 검증을 더욱 단단하게, 메뉴는 반드시 골라야 하고
이름은 필수, 수량은 최소 하나, 이제 명확해
오래된 레이아웃은 지우고, 비즈니스는 정확하게
꼼꼼한 마음으로 다시 다지는 품질 개선 ✨

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning 작업 개요는 구체적이나 테스트 내용이 완전히 누락되어 있고 기능 개발 등의 항목이 체크되지 않아 필수 섹션이 불완전합니다. 테스트 내용을 작성하고(예: Postman/Swagger 테스트 결과) 작업 내용 체크리스트의 해당 항목에 체크를 표시하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive 제목은 PR의 실제 주요 변경사항(사업자번호 인증 시 이름 입력 필드 추가 및 검증 로직 개선)을 포괄적으로 설명하지 못하고 'develop 브랜치 최신 변경사항 main 반영'으로만 표현되어 있습니다. 제목을 '[REFACTOR]: 사업자번호 인증 시 대표자 이름 필드 추가' 또는 유사한 구체적 내용으로 수정하여 주요 변경사항을 명확히 전달하세요.

✏️ 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.

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: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java (1)

11-13: 🛠️ Refactor suggestion | 🟠 Major

IllegalArgumentException 대신 커스텀 예외를 사용하세요.

IllegalArgumentException은 표준 RuntimeException의 서브클래스입니다. 코딩 가이드라인에 따라 GlobalExceptionHandler 또는 ApiResponse와 연동되는 의미 있는 커스텀 예외(예: InvalidTokenException)를 사용하는 것이 바람직합니다.

♻️ 커스텀 예외 적용 예시
-        if (refreshToken == null || refreshToken.isBlank()) {
-            throw new IllegalArgumentException("refreshToken must not be blank");
-        }
+        if (refreshToken == null || refreshToken.isBlank()) {
+            throw new InvalidTokenException("refreshToken must not be blank");
+        }

As per coding guidelines, 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/global/auth/AuthCookieProvider.java`
around lines 11 - 13, In AuthCookieProvider (the method that currently checks
refreshToken and throws IllegalArgumentException), replace the
IllegalArgumentException with a meaningful custom exception (e.g.,
InvalidTokenException) and throw that when refreshToken is null or blank; create
the InvalidTokenException class (extending the appropriate base used by your
error handling stack so it integrates with GlobalExceptionHandler/ApiResponse)
and ensure the new exception is imported and used in AuthCookieProvider so the
GlobalExceptionHandler can map it to a proper API response.
src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java (2)

73-74: ⚠️ Potential issue | 🟡 Minor

LocalDate.now()LocalTime.now()를 별도 호출하면 자정 경계에서 날짜/시간이 불일치할 수 있음

두 호출 사이에 자정이 지나면 날짜는 이전 날, 시간은 다음 날의 값을 가지게 되어 findTableIdsWithFutureBookings 쿼리의 기준이 틀어집니다. LocalDateTime.now()로 단일 호출 후 분해하거나, LocalDateTime 그대로 리포지토리에 전달하는 방식을 권장합니다.

🛠️ 제안 수정
 private boolean checkFutureBookingsInLayout(TableLayout layout) {
-    LocalDate currentDate = LocalDate.now();
-    LocalTime currentTime = LocalTime.now();
+    LocalDateTime now = LocalDateTime.now();
+    LocalDate currentDate = now.toLocalDate();
+    LocalTime currentTime = now.toLocalTime();
     List<Long> tableIds = layout.getTables().stream()
             .map(StoreTable::getId)
             .toList();
🤖 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 73 - 74, 현재 TableLayoutCommandServiceImpl에서 LocalDate currentDate =
LocalDate.now()와 LocalTime currentTime = LocalTime.now()를 별도 호출해 사용하고 있는데, 자정
경계에서 불일치가 발생할 수 있으므로 LocalDateTime.now() 한 번만 호출해 분해하거나 그 LocalDateTime을 그대로
사용하도록 변경하세요; 예를 들어 LocalDateTime now = LocalDateTime.now()를 얻은 뒤
now.toLocalDate(), now.toLocalTime()로 나누거나 findTableIdsWithFutureBookings 호출에
LocalDateTime을 전달하여 날짜/시간 기준이 일관되도록 수정하십시오.

43-54: ⚠️ Potential issue | 🟠 Major

[동시성] 미래 예약 확인과 삭제 사이에 TOCTOU 경쟁 조건 존재

checkFutureBookingsInLayout() 호출(Line 45)과 tableLayoutRepository.delete()(Line 52) 사이에 다른 트랜잭션이 예약을 커밋하면, 예약이 존재하는 상태에서 레이아웃과 테이블이 삭제됩니다. findByStoreIdAndIsActiveTrue() 메서드는 비관적 잠금(@Lock)이 없고, TableLayout 엔티티에는 낙관적 잠금(@Version)도 없어서, 기본 격리 수준(READ COMMITTED)에서 두 작업이 원자적으로 처리되지 않습니다.

해결 방법:
TableLayoutRepositoryfindByStoreIdAndIsActiveTrue() 메서드에 @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션을 추가하여 읽기와 삭제를 원자적으로 묶으세요.

🤖 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 current flow calls
checkFutureBookingsInLayout(existingLayout.get()) and then
tableLayoutRepository.delete(existingLayout.get()), which creates a TOCTOU race
where another transaction can add bookings between the check and delete; update
TableLayoutRepository's findByStoreIdAndIsActiveTrue() to acquire a pessimistic
write lock by annotating that method with `@Lock`(LockModeType.PESSIMISTIC_WRITE)
so the loaded TableLayout row is locked for the duration of the transaction,
preventing concurrent booking commits between checkFutureBookingsInLayout and
tableLayoutRepository.delete; ensure the service method runs within a
transactional boundary so the lock is effective.
src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java (1)

244-261: ⚠️ Potential issue | 🟡 Minor

사업자 인증 시 대표자 이름 검증 방식 변경 관련 보안 고려사항

이 변경은 계정 이름과 사업자 대표자명이 다른 경우를 지원하기 위한 의도적 설계임을 확인했습니다. 해당 엔드포인트는 JWT 인증을 필수로 요구하고 있고, 사업자번호 검증은 공식 정부 API(odcloud.kr)를 통해 실시간으로 진행되므로 완전히 검증되지 않은 정보로 ROLE_OWNER을 획득하기는 어렵습니다.

다만, 다음 두 가지 개선을 권장합니다.

  • 감사 로깅 강화: 현재 로그에는 userId와 email만 기록되고 있습니다. 인증 성공/실패 시 시도된 대표자명사업자번호(마스킹 처리)를 감사 로그에 포함하면 비정상 행동 패턴 감지가 용이합니다.
  • 응답 요청 속도 제한 확인: 외부 API가 자체 속도 제한을 제공하더라도, 애플리케이션 레벨에서의 명시적 rate-limiting 정책이 문서화되어 있으면 보안 투명성이 높아집니다.
🤖 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 244 - 261, Enhance audit logging and enforce/apply rate-limiting in
verifyOwner: when handling
UserServiceImpl.verifyOwner(UserRequestDto.VerifyOwnerDto dto,
HttpServletRequest request), include masked business number and the provided
representative name in the success and failure logs (use a deterministic mask
function to keep only last N digits of dto.getBusinessNumber()); keep existing
user id and email logs; ensure businessNumberValidator.validate(...) errors are
also logged with masked business number and representative name before throwing
AuthException; and add or call an application-level rate limiter (e.g.,
RateLimiterService.checkAndConsume(request.getRemoteAddr()) or similar) at the
start of verifyOwner to throttle repeated calls and return a rate-limit response
when exceeded.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/deploy.yml:
- Around line 85-87: Replace the aggressive global prune command `docker system
prune -a -f` with a safer, scoped prune and move it after the compose teardown:
remove the `-a` flag (or use `docker image prune -f`) so only dangling images
are removed, and relocate the prune step to run after `docker compose
down`/after `docker pull` so the just-replaced old image becomes dangling and is
cleaned within the same deployment cycle; update the workflow to call the scoped
prune command at that later point (reference the existing `docker system prune
-a -f`, `docker image prune -f`, `docker pull`, and `docker compose down`/`up
-d` steps).

In `@README.md`:
- Around line 77-78: Add a blank line between closing code fences and the
closing details tag where a code fence (` ``` `) is immediately followed by
`</details>` to satisfy MD031; locate the two occurrences where ` ``` ` is
directly adjacent to `</details>` (both near the earlier and later blocks) and
insert a single empty line after each closing fence so the sequence becomes `
``` `, blank line, then `</details>`.
- Line 80: README.md 파일의 해당 줄에 후행 공백(스페이스 한 칸)이 남아 있으니 해당 줄의 끝에서 불필요한 공백을 삭제하거나
줄바꿈 의도라면 후행 공백을 2칸으로 통일해 수정하세요.
- Line 144: The heading "## 🛠️ 팀원 정보 ##" uses ATX-closed style (trailing "##")
while the rest of the document uses ATX style; remove the trailing "##" so it
becomes "## 🛠️ 팀원 정보" to match the other headings and MD003 rules.
- Around line 160-162: README contains a wrong directory name after cloning: the
diff shows "git clone https://github.com/Eatsfine/BE.git" followed by "cd
eatsfine-be", but the clone creates a "BE" folder. Update the README to either
change "cd eatsfine-be" to "cd BE" or modify the clone command to "git clone
https://github.com/Eatsfine/BE.git eatsfine-be" so the directory names match;
ensure both the "git clone" and "cd" lines are consistent.
- Around line 170-171: The <details> block's <summary> currently reads "폴더 구조
펼치기/접기" but its content is an application-local.yml YAML example; update the
<summary> text inside the <details> tag to accurately describe the content
(e.g., "application-local.yml 예시" or "YAML 설정 예시") so the summary matches the
enclosed configuration snippet and improves clarity.
- Line 114: Update the commit example git commit -m "feat: 자세한 내용 적기" to match
the project's commit rule format by replacing it with the bracketed uppercase
style used elsewhere (e.g. git commit -m "[FEAT]: 자세한 내용 적기"), ensuring the
casing and bracket punctuation match the existing example "[FEAT]: 예약 생성 API 구현"
so the README shows a consistent commit message convention.

In
`@src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java`:
- Around line 14-17: The name field in BusinessNumberReqDto currently only
checks length and blankness, so update the 'name' field validation by adding a
`@Pattern` annotation (e.g., allow only Korean and English letters and optional
spaces) to reject numeric/special-character inputs; add a clear message like
"이름은 한글 또는 영문만 허용됩니다." and ensure the annotation is placed alongside `@NotBlank`
and `@Size` on the 'name' field to enforce the restriction.

In
`@src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java`:
- Around line 55-58: The change calling
businessNumberValidator.validate(dto.businessNumberDto().businessNumber(),
dto.businessNumberDto().startDate(), dto.businessNumberDto().name()) is
functionally fine but introduces two issues: it trusts a client-supplied name
(same concern as UserServiceImpl.verifyOwner change) and it executes an
external-validation call inside a class-level `@Transactional` method
(StoreCommandServiceImpl) which can hold DB connections during slow HTTP calls.
Fix by using the authenticated server-side name (e.g., the current User
principal / user.getName()) instead of dto.businessNumberDto().name() when
asserting ownership, and move the businessNumberValidator.validate(...) out of
the transactional boundary—either call it before entering the `@Transactional`
createStore flow or put it in a separate method with Propagation.REQUIRES_NEW to
ensure external HTTP validation does not run inside the transaction.

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java`:
- Around line 88-91: UserRequestDto.name currently only has `@Size` and will
accept numeric/special-only values; add a name pattern validation equivalent to
BusinessNumberReqDto.BusinessNumberDto.name (e.g., a `@Pattern` that restricts to
letters/Korean/allowed punctuation and disallows all-digit/special-only inputs)
and apply it to VerifyOwnerDto.name as well, then extract that pattern into a
reusable custom constraint annotation (e.g., `@ValidName` or `@NamePattern`) with a
shared validator so both UserRequestDto.name and
BusinessNumberReqDto.BusinessNumberDto.name / VerifyOwnerDto.name reuse the same
rule.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java`:
- Line 19: AuthCookieProvider currently hardcodes the cookie domain in two
places via .domain(".eatsfine.co.kr"); remove these magic strings and add a
private String field (e.g., cookieDomain) injected with
`@Value`("${cookie.domain}") in the AuthCookieProvider class, then replace both
.domain(...) calls in the methods that build cookies with this injected
cookieDomain; ensure application-*.yml defines cookie.domain for each
environment so local/staging/prod can vary.
- Around line 15-21: Extract the hardcoded cookie name and expiration into
private static final constants in AuthCookieProvider (e.g.,
REFRESH_TOKEN_COOKIE_NAME = "refreshToken" and REFRESH_TOKEN_MAX_AGE =
Duration.ofDays(14) or REFRESH_TOKEN_MAX_AGE_DAYS = 14L) and replace the literal
occurrences inside the ResponseCookie.from(...) and .maxAge(...) calls with
these constants so both methods reference the single source of truth; keep
constants private, static and final and update any other uses in the class to
use the new constants.

---

Outside diff comments:
In
`@src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java`:
- Around line 73-74: 현재 TableLayoutCommandServiceImpl에서 LocalDate currentDate =
LocalDate.now()와 LocalTime currentTime = LocalTime.now()를 별도 호출해 사용하고 있는데, 자정
경계에서 불일치가 발생할 수 있으므로 LocalDateTime.now() 한 번만 호출해 분해하거나 그 LocalDateTime을 그대로
사용하도록 변경하세요; 예를 들어 LocalDateTime now = LocalDateTime.now()를 얻은 뒤
now.toLocalDate(), now.toLocalTime()로 나누거나 findTableIdsWithFutureBookings 호출에
LocalDateTime을 전달하여 날짜/시간 기준이 일관되도록 수정하십시오.
- Around line 43-54: The current flow calls
checkFutureBookingsInLayout(existingLayout.get()) and then
tableLayoutRepository.delete(existingLayout.get()), which creates a TOCTOU race
where another transaction can add bookings between the check and delete; update
TableLayoutRepository's findByStoreIdAndIsActiveTrue() to acquire a pessimistic
write lock by annotating that method with `@Lock`(LockModeType.PESSIMISTIC_WRITE)
so the loaded TableLayout row is locked for the duration of the transaction,
preventing concurrent booking commits between checkFutureBookingsInLayout and
tableLayoutRepository.delete; ensure the service method runs within a
transactional boundary so the lock is effective.

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java`:
- Around line 244-261: Enhance audit logging and enforce/apply rate-limiting in
verifyOwner: when handling
UserServiceImpl.verifyOwner(UserRequestDto.VerifyOwnerDto dto,
HttpServletRequest request), include masked business number and the provided
representative name in the success and failure logs (use a deterministic mask
function to keep only last N digits of dto.getBusinessNumber()); keep existing
user id and email logs; ensure businessNumberValidator.validate(...) errors are
also logged with masked business number and representative name before throwing
AuthException; and add or call an application-level rate limiter (e.g.,
RateLimiterService.checkAndConsume(request.getRemoteAddr()) or similar) at the
start of verifyOwner to throttle repeated calls and return a rate-limit response
when exceeded.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java`:
- Around line 11-13: In AuthCookieProvider (the method that currently checks
refreshToken and throws IllegalArgumentException), replace the
IllegalArgumentException with a meaningful custom exception (e.g.,
InvalidTokenException) and throw that when refreshToken is null or blank; create
the InvalidTokenException class (extending the appropriate base used by your
error handling stack so it integrates with GlobalExceptionHandler/ApiResponse)
and ensure the new exception is imported and used in AuthCookieProvider so the
GlobalExceptionHandler can map it to a proper API response.

Comment on lines +85 to +87
# 불필요한 도커 이미지 및 컨테이너 정리
docker system prune -a -f || true

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

docker system prune -a -f-a 플래그가 과도하게 공격적이며, 실행 위치도 최적이 아닙니다.

두 가지 문제가 있습니다.

1. -a 플래그 — 다른 서비스 이미지 제거 위험

docker system prune -a -f 는 실행 중인 컨테이너에 연결된 이미지를 제외하고 모든 미사용 이미지를 삭제합니다. EC2 호스트에 다른 Docker 서비스(예: nginx, DB 등)가 존재하고 해당 컨테이너가 배포 시점에 잠시라도 중지되어 있다면, 해당 서비스의 이미지가 영구적으로 삭제됩니다.

불필요한 레이어(dangling 이미지)만 제거하려면 -a 없이 사용하거나 docker image prune -f 로 범위를 제한하는 것이 안전합니다.

2. 실행 위치 — 현재 배포의 구 이미지가 정리되지 않음

현재 실행 순서:

  1. docker system prune -a -f ← 컨테이너가 아직 실행 중이므로 eatsfine-be:latest 이미지는 보호됨
  2. docker pull ← 새 이미지가 latest 태그를 가져가고, 구 이미지는 dangling 상태가 됨
  3. docker compose down / up -d

즉, 이번 배포에서 생긴 dangling 구 이미지는 이미 prune이 끝난 뒤 발생하기 때문에 다음 배포 때까지 디스크에 남습니다. 이전 방식(docker compose down 이후 prune)이 오히려 같은 사이클 내에서 구 이미지를 정리하는 데 더 효과적이었습니다.

아래와 같이 수정하면 두 문제를 모두 완화할 수 있습니다.

🛠️ 제안된 수정안
           docker pull ${{ secrets.DOCKERHUB_USERNAME }}/eatsfine-be:latest
         
           docker compose down
+
+          # 불필요한 dangling 이미지 및 중지된 컨테이너 정리 (다른 서비스 이미지는 유지)
+          docker system prune -f || true
+
           docker compose up -d
         
           docker ps
-          # 불필요한 도커 이미지 및 컨테이너 정리
-          docker system prune -a -f || true
-
           docker pull ${{ secrets.DOCKERHUB_USERNAME }}/eatsfine-be:latest
  • -a 제거: dangling 이미지만 삭제하여 다른 서비스 이미지를 보호합니다.
  • docker compose down 이후로 이동: 구 이미지가 dangling 상태가 된 직후 같은 사이클 내에서 정리됩니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy.yml around lines 85 - 87, Replace the aggressive
global prune command `docker system prune -a -f` with a safer, scoped prune and
move it after the compose teardown: remove the `-a` flag (or use `docker image
prune -f`) so only dangling images are removed, and relocate the prune step to
run after `docker compose down`/after `docker pull` so the just-replaced old
image becomes dangling and is cleaned within the same deployment cycle; update
the workflow to call the scoped prune command at that later point (reference the
existing `docker system prune -a -f`, `docker image prune -f`, `docker pull`,
and `docker compose down`/`up -d` steps).

Comment on lines +77 to +78
```
</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

코드 블록 전후 빈 줄 누락 (MD031).

Lines 77–78과 Lines 245–246에서 닫는 코드 펜스(```) 바로 다음에 </details> 태그가 오고 있어 Markdown 렌더링 시 경고가 발생합니다. 코드 블록 뒤에 빈 줄을 추가하세요.

📝 수정 제안
``` (Lines 77–78 및 Lines 245–246 모두 동일하게 적용)

Also applies to: 245-246

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 77-77: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 77 - 78, Add a blank line between closing code fences
and the closing details tag where a code fence (` ``` `) is immediately followed
by `</details>` to satisfy MD031; locate the two occurrences where ` ``` ` is
directly adjacent to `</details>` (both near the earlier and later blocks) and
insert a single empty line after each closing fence so the sequence becomes `
``` `, blank line, then `</details>`.

```
</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

후행 공백 제거 필요 (MD009).

Line 80에 불필요한 후행 공백이 1개 존재합니다. 해당 줄의 공백을 제거하거나 줄바꿈 의도라면 2칸으로 통일하세요.

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[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` at line 80, README.md 파일의 해당 줄에 후행 공백(스페이스 한 칸)이 남아 있으니 해당 줄의 끝에서
불필요한 공백을 삭제하거나 줄바꿈 의도라면 후행 공백을 2칸으로 통일해 수정하세요.


```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

커밋 예시 형식이 프로젝트 규칙과 다릅니다.

Line 114의 예시 "feat: 자세한 내용 적기"는 소문자에 대괄호가 없는 형식이지만, 커밋 규칙 섹션(Lines 38–40)에서는 [FEAT]: 예약 생성 API 구현 형식을 사용합니다. 문서 내 일관성을 위해 통일이 필요합니다.

📝 수정 제안
-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 git commit -m "feat: 자세한 내용
적기" to match the project's commit rule format by replacing it with the bracketed
uppercase style used elsewhere (e.g. git commit -m "[FEAT]: 자세한 내용 적기"),
ensuring the casing and bracket punctuation match the existing example "[FEAT]:
예약 생성 API 구현" so the README shows a consistent commit message convention.

- PR에 작업 요약 + 테스트 결과 포함하기
- 충돌 발생 시 브랜치에서 먼저 해결 후 PR 업데이트

## 🛠️ 팀원 정보 ##
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

ATX-closed 헤딩 스타일 사용 (MD003).

Line 144의 ## 🛠️ 팀원 정보 ##는 trailing ##가 붙은 ATX-closed 형식입니다. 나머지 헤딩은 모두 ATX 형식이므로 통일이 필요합니다.

📝 수정 제안
-## 🛠️ 팀원 정보 ##
+## 🛠️ 팀원 정보
📝 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
## 🛠️ 팀원 정보 ##
## 🛠️ 팀원 정보
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 144-144: Heading style
Expected: atx; Actual: atx_closed

(MD003, heading-style)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 144, The heading "## 🛠️ 팀원 정보 ##" uses ATX-closed style
(trailing "##") while the rest of the document uses ATX style; remove the
trailing "##" so it becomes "## 🛠️ 팀원 정보" to match the other headings and MD003
rules.

Comment on lines +14 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.

🧹 Nitpick | 🔵 Trivial

name 필드에 문자 유형 제한 @Pattern 추가 권장

@Size(min = 2, max = 20)은 길이만 검증하므로 "1234567"과 같은 숫자 문자열이나 특수문자 조합도 유효한 이름으로 통과됩니다. 대표자 성명은 한글 또는 영문으로만 구성되어야 하므로 아래처럼 @Pattern을 추가하세요.

✏️ 제안 수정
+           `@Pattern`(regexp = "^[가-힣a-zA-Z\\s]{2,20}$", message = "이름은 한글 또는 영문만 입력 가능합니다.")
            `@Size`(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
            String name,

As per coding guidelines, 입력 검증 및 보안 항목 — "@Valid, Bean Validation 등을 통한 입력값 검증이 되어 있는지" 확인 요망.

📝 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
@Schema(description = "이름", example = "홍길동")
@NotBlank(message = "이름은 필수입니다.")
@Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
String name,
`@Schema`(description = "이름", example = "홍길동")
`@NotBlank`(message = "이름은 필수입니다.")
`@Pattern`(regexp = "^[가-힣a-zA-Z\\s]{2,20}$", message = "이름은 한글 또는 영문만 입력 가능합니다.")
`@Size`(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
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/businessnumber/dto/BusinessNumberReqDto.java`
around lines 14 - 17, The name field in BusinessNumberReqDto currently only
checks length and blankness, so update the 'name' field validation by adding a
`@Pattern` annotation (e.g., allow only Korean and English letters and optional
spaces) to reject numeric/special-character inputs; add a clear message like
"이름은 한글 또는 영문만 허용됩니다." and ensure the annotation is placed alongside `@NotBlank`
and `@Size` on the 'name' field to enforce the restriction.

Comment on lines 55 to +58
businessNumberValidator.validate(
dto.businessNumberDto().businessNumber(),
dto.businessNumberDto().startDate(),
user.getName());
dto.businessNumberDto().name());
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

dto.businessNumberDto().name() 변경은 일관성 있음 — 단, @Transactional 내 외부 API 호출 주의

Line 58의 변경(dto.businessNumberDto().name())은 UserServiceImpl.verifyOwner의 변경과 일관되며 코드 자체는 올바릅니다. 단, 동일한 보안 고려사항(user.getName() → DTO 입력값 사용으로 인한 인증 신뢰 약화)이 여기서도 동일하게 적용됩니다.

추가로, StoreCommandServiceImpl에 클래스 레벨 @Transactional이 적용되어 있어 businessNumberValidator.validate() (외부 HTTP API 호출로 추정)이 트랜잭션 내에서 실행됩니다. 외부 API 응답이 지연될 경우 DB 커넥션이 그대로 점유되어 커넥션 풀 고갈로 이어질 수 있습니다.

💡 개선 방향 예시
// 외부 API 검증을 트랜잭션 시작 전에 수행하도록 분리
// Option: `@Transactional`(propagation = Propagation.REQUIRES_NEW) 별도 메서드, 
// 또는 createStore 내에서 validate() 후 트랜잭션 시작
🤖 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 55 - 58, The change calling
businessNumberValidator.validate(dto.businessNumberDto().businessNumber(),
dto.businessNumberDto().startDate(), dto.businessNumberDto().name()) is
functionally fine but introduces two issues: it trusts a client-supplied name
(same concern as UserServiceImpl.verifyOwner change) and it executes an
external-validation call inside a class-level `@Transactional` method
(StoreCommandServiceImpl) which can hold DB connections during slow HTTP calls.
Fix by using the authenticated server-side name (e.g., the current User
principal / user.getName()) instead of dto.businessNumberDto().name() when
asserting ownership, and move the businessNumberValidator.validate(...) out of
the transactional boundary—either call it before entering the `@Transactional`
createStore flow or put it in a separate method with Propagation.REQUIRES_NEW to
ensure external HTTP validation does not run inside the transaction.

Comment on lines +88 to +91
@Schema(description = "이름", example = "홍길동")
@NotBlank(message = "이름은 필수입니다.")
@Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
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.

🧹 Nitpick | 🔵 Trivial

name 필드 패턴 검증 누락 — BusinessNumberReqDto와 동일한 문제

VerifyOwnerDto.name@Size(2, 20)만 있어 숫자·특수문자로만 구성된 값이 검증을 통과합니다. BusinessNumberReqDto.BusinessNumberDto.name과 동일한 검증 규칙이 두 곳에 분산되어 있으므로, 패턴 검증 추가와 함께 공통 Validator 어노테이션으로 추출하는 것을 고려해 주세요.

✏️ 제안 수정
         `@Schema`(description = "이름", example = "홍길동")
         `@NotBlank`(message = "이름은 필수입니다.")
+        `@Pattern`(regexp = "^[가-힣a-zA-Z\\s]{2,20}$", message = "이름은 한글 또는 영문만 입력 가능합니다.")
         `@Size`(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
         private String name;

As per coding guidelines, 입력 검증 및 보안 항목 — "Bean Validation 등을 통한 입력값 검증이 되어 있는지" 확인.

📝 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
@Schema(description = "이름", example = "홍길동")
@NotBlank(message = "이름은 필수입니다.")
@Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
private String name;
`@Schema`(description = "이름", example = "홍길동")
`@NotBlank`(message = "이름은 필수입니다.")
`@Pattern`(regexp = "^[가-힣a-zA-Z\\s]{2,20}$", 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 88 - 91, UserRequestDto.name currently only has `@Size` and will
accept numeric/special-only values; add a name pattern validation equivalent to
BusinessNumberReqDto.BusinessNumberDto.name (e.g., a `@Pattern` that restricts to
letters/Korean/allowed punctuation and disallows all-digit/special-only inputs)
and apply it to VerifyOwnerDto.name as well, then extract that pattern into a
reusable custom constraint annotation (e.g., `@ValidName` or `@NamePattern`) with a
shared validator so both UserRequestDto.name and
BusinessNumberReqDto.BusinessNumberDto.name / VerifyOwnerDto.name reuse the same
rule.

Comment on lines 15 to 21
return ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.secure(true)
.sameSite("None") // Lax → None
.domain(".eatsfine.co.kr") //로컬 개발로 인해 주석 처리
.sameSite("None")
.domain(".eatsfine.co.kr")
.path("/")
.maxAge(Duration.ofDays(14))
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

쿠키 이름과 만료 기간을 상수로 추출하세요.

"refreshToken" (line 15, 26)과 Duration.ofDays(14) (line 21)이 두 메서드에 걸쳐 중복 하드코딩되어 있습니다. 상수로 추출하면 변경 시 한 곳만 수정하면 됩니다.

♻️ 상수 추출 예시
 `@Component`
 public class AuthCookieProvider {
+    private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";
+    private static final Duration REFRESH_TOKEN_MAX_AGE = Duration.ofDays(14);
+
     public ResponseCookie refreshTokenCookie(String refreshToken) {
         ...
-        return ResponseCookie.from("refreshToken", refreshToken)
+        return ResponseCookie.from(REFRESH_TOKEN_COOKIE_NAME, refreshToken)
                 ...
-                .maxAge(Duration.ofDays(14))
+                .maxAge(REFRESH_TOKEN_MAX_AGE)
                 .build();
     }

     public ResponseCookie clearRefreshTokenCookie() {
-        return ResponseCookie.from("refreshToken", "")
+        return ResponseCookie.from(REFRESH_TOKEN_COOKIE_NAME, "")
                 ...
     }
 }

As per coding guidelines, 매직 넘버, 하드코딩된 값이 존재하는지 점검.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java`
around lines 15 - 21, Extract the hardcoded cookie name and expiration into
private static final constants in AuthCookieProvider (e.g.,
REFRESH_TOKEN_COOKIE_NAME = "refreshToken" and REFRESH_TOKEN_MAX_AGE =
Duration.ofDays(14) or REFRESH_TOKEN_MAX_AGE_DAYS = 14L) and replace the literal
occurrences inside the ResponseCookie.from(...) and .maxAge(...) calls with
these constants so both methods reference the single source of truth; keep
constants private, static and final and update any other uses in the class to
use the new constants.

.sameSite("None") // Lax → None
.domain(".eatsfine.co.kr") //로컬 개발로 인해 주석 처리
.sameSite("None")
.domain(".eatsfine.co.kr")
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

도메인 값을 외부 설정으로 분리하세요.

.domain(".eatsfine.co.kr")이 두 메서드(line 19, line 30) 모두에 하드코딩되어 있습니다. 삭제된 주석(//로컬 개발로 인해 주석 처리)이 시사하듯, 로컬·스테이징·프로덕션 환경마다 도메인이 달라 수동 토글이 필요했던 것으로 보입니다. @Value로 외부화하면 환경별 설정 파일(application-local.yml, application-prod.yml)로 자연스럽게 분기할 수 있습니다.

♻️ `@Value를` 통한 도메인 외부화 예시
+import org.springframework.beans.factory.annotation.Value;
+
 `@Component`
 public class AuthCookieProvider {
+
+    `@Value`("${cookie.domain:.eatsfine.co.kr}")
+    private String cookieDomain;
+
     public ResponseCookie refreshTokenCookie(String refreshToken) {
         ...
         return ResponseCookie.from("refreshToken", refreshToken)
                 .httpOnly(true)
                 .secure(true)
                 .sameSite("None")
-                .domain(".eatsfine.co.kr")
+                .domain(cookieDomain)
                 ...
     }

     public ResponseCookie clearRefreshTokenCookie() {
         return ResponseCookie.from("refreshToken", "")
                 ...
-                .domain(".eatsfine.co.kr")
+                .domain(cookieDomain)
                 ...
     }
 }

application-local.yml:

cookie:
  domain: localhost

application-prod.yml:

cookie:
  domain: .eatsfine.co.kr

As per coding guidelines, 매직 넘버, 하드코딩된 값이 존재하는지 점검.

📝 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
.domain(".eatsfine.co.kr")
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Component;
`@Component`
public class AuthCookieProvider {
`@Value`("${cookie.domain:.eatsfine.co.kr}")
private String cookieDomain;
public ResponseCookie refreshTokenCookie(String refreshToken) {
return ResponseCookie.from("refreshToken", refreshToken)
.httpOnly(true)
.secure(true)
.sameSite("None")
.domain(cookieDomain)
.path("/")
.maxAge(7 * 24 * 60 * 60) // 7 days
.build();
}
public ResponseCookie clearRefreshTokenCookie() {
return ResponseCookie.from("refreshToken", "")
.httpOnly(true)
.secure(true)
.sameSite("None")
.domain(cookieDomain)
.path("/")
.maxAge(0)
.build();
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java` at
line 19, AuthCookieProvider currently hardcodes the cookie domain in two places
via .domain(".eatsfine.co.kr"); remove these magic strings and add a private
String field (e.g., cookieDomain) injected with `@Value`("${cookie.domain}") in
the AuthCookieProvider class, then replace both .domain(...) calls in the
methods that build cookies with this injected cookieDomain; ensure
application-*.yml defines cookie.domain for each environment so
local/staging/prod can vary.

@twodo0 twodo0 merged commit cd8a2f5 into main Feb 18, 2026
2 checks passed
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