diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 9c230820..827f50f5 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -82,14 +82,14 @@ jobs:
set -e
cd /home/ec2-user/deploy
+ # 불필요한 도커 이미지 및 컨테이너 정리
+ docker system prune -a -f || true
+
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/eatsfine-be:latest
docker compose down
docker compose up -d
- # 불필요한 도커 이미지 및 컨테이너 정리
- docker image prune -f
-
docker ps
EOF
- name: GitHub Actions - SSH 및 컨테이너 실제 포트 접근 권한 제거
diff --git a/README.md b/README.md
index f708e3a5..df5252e6 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,257 @@
-# BE
-Eatsfine-BackEnd
+# 🍽️ Eatsfine BE
+
+**Eatsfine 백엔드 레포지토리입니다.**
+
+🖇️**Service URL**
+https://www.eatsfine.co.kr
+
+📚**API Documentation**
+https://eatsfine.co.kr/swagger-ui/index.html
+
+- Language: **Java 21**
+- Framework: **Spring Boot 3.4.1**
+- Database: **MySQL 8.0**, **Redis**
+- ORM: **Spring Data JPA**, **QueryDSL 5.1.0**
+- Security: **Spring Security**, **OAuth2 Client**, **JWT**
+- Cloud & Infra: **AWS S3**, **Docker**, **GitHub Actions**
+- Build Tool: **Gradle**
+
+## 🔥 Git Commit Convention (커밋 규칙)
+
+효율적인 협업을 위해 다음과 같은 커밋 메세지 규칙을 사용합니다.
+
+**type은 대문자로 통일합니다.**
+
+| 커밋 타입 | 설명 |
+| ------------- | ------------------------------ |
+| 🎉 `FEAT` | 새로운 기능 추가 |
+| 🐛 `FIX` | 버그/오류 수정 |
+| 🛠 `CHORE` | 코드/내부 파일/설정 수정 |
+| 📝 `DOCS` | 문서 수정 (README 등) |
+| 🔄 `REFACTOR` | 코드 리팩토링 (기능 변경 없음) |
+| 🧪 `TEST` | 테스트 코드 추가/수정 |
+| 🎨 `STYLE` | 스타일 변경(포맷, 세미콜론 등) |
+
+💻 **예시**
+
+```bash
+git commit -m "[FEAT]: 예약 생성 API 구현"
+git commit -m "[FIX]: OAuth2 로그인 리다이렉트 오류 수정"
+git commit -m "[CHORE]: SecurityConfig CORS 설정 변경"
+```
+
+## 📁 폴더 구조
+
+
+ 폴더 구조 펼치기/접기
+
+```plaintext
+src/main/java/com/eatsfine/eatsfine/
+ ├── domain/ # 도메인별 비즈니스 로직
+ │ ├── booking/ # 예약 관리
+ │ ├── businesshours/ # 영업시간 관리
+ │ ├── businessnumber/ # 사업자번호 검증
+ │ ├── image/ # 이미지 처리
+ │ ├── inquiry/ # 문의 관리
+ │ ├── menu/ # 메뉴 관리
+ │ ├── payment/ # 결제 시스템
+ │ ├── region/ # 지역 관리
+ │ ├── store/ # 식당 정보 관리
+ │ ├── storetable/ # 식당 테이블 관리
+ │ ├── table_layout/ # 테이블 배치도
+ │ ├── tableblock/ # 테이블 블록 관리
+ │ ├── tableimage/ # 테이블 이미지
+ │ ├── term/ # 약관 관리
+ │ └── user/ # 사용자(회원) 관리
+ │
+ └── global/ # 전역 설정 및 공통 모듈
+ ├── annotation/ # 커스텀 어노테이션
+ ├── apiPayload/ # 공통 응답/예외 처리 (ApiResponse)
+ ├── auth/ # 보안/인증 로직 (CustomHandler 등)
+ ├── common/ # 공통 유틸리티
+ ├── config/ # 설정 파일 (Security, Swagger, QueryDSL 등)
+ ├── controller/ # 공통 컨트롤러 (HealthCheck)
+ ├── resolver/ # Argument Resolver
+ ├── s3/ # AWS S3 연동
+ └── validator/ # 커스텀 검증기
+```
+
+
+
+## 🌿 Branch
+
+- main : 배포/최종 안정 브랜치 **(직접 push 금지)**
+- develop: 개발 통합 브랜치 (기본 작업 브랜치)
+- 작업 브랜치 네이밍:
+ - `feat/booking-api`
+ - `fix/oauth-login`
+ - `chore/swagger-config`
+ - `refactor/payment-service`
+
+## 🎯 작업 루틴
+
+기본 브랜치는 develop
+
+작업은 항상 `develop`에서 브랜치를 따서 진행하고, PR은 develop으로 올립니다.
+
+### 1. 작업 시작 전 (최신화)
+
+```bash
+git checkout develop
+git pull --rebase origin develop
+```
+
+### 2. 작업 브랜치 생성
+
+```bash
+git checkout -b feat/featureName
+```
+
+### 3. 작업 후 커밋 & 푸시
+
+```bash
+git add . # 필요하면 git add file명 으로 특정 파일만 추가해도 됨
+git commit -m "feat: 자세한 내용 적기"
+git push -u origin feat/featureName
+```
+
+### 4. PR 생성
+
+- feat/ → develop 로 PR 생성
+- PR 본문에 Closes #이슈번호 작성해서 merge 시 이슈가 자동으로 닫히도록 설정
+
+```md
+Closes #이슈번호
+```
+
+### 5. 리뷰 & 머지
+
+- 최소 2명 승인 후 merge
+- main은 배포/최종용 브랜치이기에 **직접 push 금지**
+
+## 🔒 보안
+
+- `application.yml` 및 민감정보는 절대 커밋 금지
+- 공유가 필요한 환경변수는 `application-local.yml` 등을 통해 관리하거나 노션/슬랙을 통해 공유합니다.
+
+## 👥 팀 규칙
+
+- **작업 시작전 develop 최신화: git pull --rebase origin develop**
+- PR은 가능한 작게 쪼개서 올리기
+- PR에 작업 요약 + 테스트 결과 포함하기
+- 충돌 발생 시 브랜치에서 먼저 해결 후 PR 업데이트
+
+## 🛠️ 팀원 정보 ##
+
+| 이름 | 주요 담당 업무 |
+| --------- |-----------------------------------------------------|
+| 민토리 / 성민주 | 회원/인증, JWT 등 공통 보안 설정, 공통 응답 + 예외 처리 |
+| 앤디 / 박영찬 | CI/CD, 토스 페이먼츠 결제 위젯 연동, 1:1 문의 |
+| 영도 / 이도영 | 식당 도메인, OpenAPI 활용 사업자 인증, RBAC 기반 권한 제어, AWS S3 연동 |
+| 준 / 손준규 | 식당 배치도, 테이블 CRUD 개발 및 테이블별 예약 가능 시간대 관리 |
+| 누리 / 정준영 | 예약 도메인 개발, 결제 프로세스와 연동 |
+
+
+## 💡 시작 방법
+
+### 1. Clone & Install
+
+```bash
+git clone https://github.com/Eatsfine/BE.git
+cd eatsfine-be
+./gradlew clean build
+```
+
+### 2. Environment Values
+
+DB 접속 정보 및 외부 API 키 등은 환경변수 또는 로컬 설정 파일로 관리합니다.
+`src/main/resources/application-local.yml`을 생성하여 필요한 설정을 추가하세요.
+
+
+ 폴더 구조 펼치기/접기
+
+```yaml
+server:
+ port: 8080
+ profile: local
+
+spring:
+ config:
+ activate:
+ on-profile: local
+ datasource:
+ url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=UTF-8&serverTimezone=Asia/Seoul
+ username: ${DB_USERNAME}
+ password: ${DB_PASSWORD}
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ data:
+ redis:
+ host: ${REDIS_HOST}
+ port: ${REDIS_PORT}
+ jpa:
+ hibernate:
+ ddl-auto: update
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+ security:
+ oauth2:
+ client:
+ registration:
+ google:
+ client-id: ${GOOGLE_CLIENT_ID}
+ client-secret: ${GOOGLE_CLIENT_SECRET}
+ scope:
+ - email
+ - profile
+ redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
+ authorization-grant-type: authorization_code
+ kakao:
+ client-id: ${KAKAO_CLIENT_ID}
+ client-secret: ${KAKAO_CLIENT_SECRET}
+ scope:
+ - profile_nickname
+ - profile_image
+ - account_email
+ redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
+ authorization-grant-type: authorization_code
+ client-name: Kakao
+ provider: kakao
+ provider:
+ google:
+ authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
+ token-uri: https://oauth2.googleapis.com/token
+ user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
+ kakao:
+ authorization-uri: https://kauth.kakao.com/oauth/authorize
+ token-uri: https://kauth.kakao.com/oauth/token
+ user-info-uri: https://kapi.kakao.com/v2/user/me
+ user-name-attribute: id
+
+payment:
+ toss:
+ widget-secret-key: ${TOSS_WIDGET_SECRET_KEY}
+
+cloud:
+ aws:
+ region: ${AWS_REGION}
+ s3:
+ bucket: ${AWS_S3_BUCKET}
+ base-url: ${AWS_S3_BASE_URL}
+
+jwt:
+ secret: ${SECRET_KEY}
+```
+
+
+### 3. Run
+
+```bash
+./gradlew bootRun
+```
+
+### 4. API Docs (Swagger)
+
+서버 실행 후 아래 주소로 접속하여 API 명세를 확인할 수 있습니다.
+- Local: http://localhost:8080/swagger-ui/index.html
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java b/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java
index 42288939..b8d6af51 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/booking/dto/request/BookingRequestDTO.java
@@ -2,8 +2,10 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.RequestParam;
@@ -36,12 +38,12 @@ public record CreateBookingDTO(
@NotNull @Min(1) Integer partySize,
@NotNull List tableIds,
@NotNull boolean isSplitAccepted,
- @NotNull List menuItems
+ @Valid @NotEmpty(message = "예약 시 메뉴 선택은 필수입니다.") List menuItems
){}
public record MenuOrderDto(
@NotNull Long menuId,
- @NotNull @Min(1) Integer quantity
+ @NotNull @Min(value = 1, message = "최소 1개 이상 주문해야 합니다.") Integer quantity
){}
public record PaymentConfirmDTO(
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java b/src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java
index b87f3f3d..98207d56 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/businessnumber/dto/BusinessNumberReqDto.java
@@ -1,13 +1,21 @@
package com.eatsfine.eatsfine.domain.businessnumber.dto;
+import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
import lombok.Builder;
public class BusinessNumberReqDto {
@Builder
public record BusinessNumberDto(
+
+ @Schema(description = "이름", example = "홍길동")
+ @NotBlank(message = "이름은 필수입니다.")
+ @Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
+ String name,
+
@NotBlank(message = "사업자번호는 필수입니다.")
@Pattern(regexp = "^[0-9]{10}$", message = "사업자번호는 숫자 10자리여야 합니다.")
String businessNumber,
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java
index 9a133a26..a6afc3e7 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/store/service/StoreCommandServiceImpl.java
@@ -55,7 +55,7 @@ public StoreResDto.StoreCreateDto createStore(StoreReqDto.StoreCreateDto dto, St
businessNumberValidator.validate(
dto.businessNumberDto().businessNumber(),
dto.businessNumberDto().startDate(),
- user.getName());
+ dto.businessNumberDto().name());
log.info("사업자 번호 검증 성공: {}", dto.businessNumberDto().businessNumber());
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java
index dbba1ca1..f676b6e1 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/table_layout/service/TableLayoutCommandServiceImpl.java
@@ -48,8 +48,10 @@ public TableLayoutResDto.LayoutDetailDto createLayout(
throw new TableLayoutException(TableLayoutErrorStatus._CANNOT_DELETE_LAYOUT_WITH_FUTURE_BOOKINGS);
}
- // 미래 예약이 없으면 배치도 비활성화 후 재생성
+ // 미래 예약이 없으면 배치도와 속해있는 테이블 삭제 (soft delete)
tableLayoutRepository.delete(existingLayout.get());
+
+ tableLayoutRepository.flush();
}
// 새 배치도 생성
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java b/src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java
index 2441d89f..65357478 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java
@@ -11,28 +11,25 @@ public class UserRequestDto {
@PasswordMatch
@Getter
- public static class JoinDto{
+ public static class JoinDto {
@NotBlank(message = "이름은 필수입니다.")
- private String name; // 이름
+ private String name; // 이름
@NotBlank(message = "이메일은 필수입니다.")
@Email(message = "유효한 이메일 형식이어야 합니다.")
- private String email; // 이메일
+ private String email; // 이메일
@NotBlank(message = "휴대전화 번호는 필수입니다.")
@Pattern(regexp = "^010\\d{8}$", message = "휴대전화 번호는 010으로 시작하는 11자리 숫자여야 합니다.")
- private String phoneNumber; // 휴대전화 번호
+ private String phoneNumber; // 휴대전화 번호
@NotBlank(message = "비밀번호는 필수 입니다.")
- @Pattern(
- regexp = "^(?=(.*[a-zA-Z].*[0-9])|(?=.*[a-zA-Z].*[!@#$%^&*])|(?=.*[0-9].*[!@#$%^&*]))[a-zA-Z0-9!@#$%^&*]{8,20}$",
- message = "비밀번호는 영문, 숫자, 특수문자 중 2가지 이상 조합이며, 8자 ~20자 이내 이어야 합니다."
- )
+ @Pattern(regexp = "^(?=(.*[a-zA-Z].*[0-9])|(?=.*[a-zA-Z].*[!@#$%^&*])|(?=.*[0-9].*[!@#$%^&*]))[a-zA-Z0-9!@#$%^&*]{8,20}$", message = "비밀번호는 영문, 숫자, 특수문자 중 2가지 이상 조합이며, 8자 ~20자 이내 이어야 합니다.")
private String password;
@NotBlank(message = "비밀번호 확인은 필수입니다.")
- private String passwordConfirm; // 비밀번호 확인
+ private String passwordConfirm; // 비밀번호 확인
@AssertTrue(message = "이용약관에 동의해야 합니다.")
@Schema(description = "서비스 이용약관 동의 여부 (필수)", example = "true")
@@ -75,10 +72,7 @@ public static class ChangePasswordDto {
private String currentPassword;
@NotBlank(message = "새 비밀번호는 필수입니다.")
- @Pattern(
- regexp = "^(?=(.*[a-zA-Z].*[0-9])|(?=.*[a-zA-Z].*[!@#$%^&*])|(?=.*[0-9].*[!@#$%^&*]))[a-zA-Z0-9!@#$%^&*]{8,20}$",
- message = "새 비밀번호는 영문, 숫자, 특수문자 중 2가지 이상 조합이며, 8자 ~20자 이내 이어야 합니다."
- )
+ @Pattern(regexp = "^(?=(.*[a-zA-Z].*[0-9])|(?=.*[a-zA-Z].*[!@#$%^&*])|(?=.*[0-9].*[!@#$%^&*]))[a-zA-Z0-9!@#$%^&*]{8,20}$", message = "새 비밀번호는 영문, 숫자, 특수문자 중 2가지 이상 조합이며, 8자 ~20자 이내 이어야 합니다.")
@Schema(description = "새 비밀번호", example = "NewPw!1234")
private String newPassword;
@@ -91,6 +85,11 @@ public static class ChangePasswordDto {
@NoArgsConstructor
public static class VerifyOwnerDto {
+ @Schema(description = "이름", example = "홍길동")
+ @NotBlank(message = "이름은 필수입니다.")
+ @Size(min = 2, max = 20, message = "이름은 2자 이상 20자 이내여야 합니다.")
+ private String name;
+
@Schema(description = "사업자번호", example = "1234567890")
@NotBlank(message = "사업자번호는 필수입니다.")
@Pattern(regexp = "^[0-9]{10}$", message = "사업자번호는 숫자 10자리여야 합니다.")
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java b/src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java
index fdd8dc38..6e411bd2 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/user/service/userService/UserServiceImpl.java
@@ -251,7 +251,7 @@ public UserResponseDto.VerifyOwnerDto verifyOwner(UserRequestDto.VerifyOwnerDto
throw new AuthException(AuthErrorStatus.ALREADY_OWNER);
}
- businessNumberValidator.validate(dto.getBusinessNumber(), dto.getStartDate(), user.getName());
+ businessNumberValidator.validate(dto.getBusinessNumber(), dto.getStartDate(), dto.getName());
user.updateToOwner();
User savedUser = userRepository.save(user);
diff --git a/src/main/java/com/eatsfine/eatsfine/domain/user/status/UserErrorStatus.java b/src/main/java/com/eatsfine/eatsfine/domain/user/status/UserErrorStatus.java
index 8eb69457..a4c8b147 100644
--- a/src/main/java/com/eatsfine/eatsfine/domain/user/status/UserErrorStatus.java
+++ b/src/main/java/com/eatsfine/eatsfine/domain/user/status/UserErrorStatus.java
@@ -18,7 +18,6 @@ public enum UserErrorStatus implements BaseErrorCode {
PASSWORD_NOT_MATCH(HttpStatus.BAD_REQUEST, "MEMBER4005", "현재 비밀번호가 일치하지 않습니다."),
SAME_PASSWORD(HttpStatus.BAD_REQUEST, "MEMBER4006", "새 비밀번호가 현재 비밀번호와 동일합니다."),
WITHDRAWN_USER(HttpStatus.FORBIDDEN, "MEMBER4007", "탈퇴한 회원입니다.")
-
;
private final HttpStatus httpStatus;
diff --git a/src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java b/src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java
index 8e47f898..087bc510 100644
--- a/src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java
+++ b/src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java
@@ -15,8 +15,8 @@ public ResponseCookie refreshTokenCookie(String refreshToken) {
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))
.build();