diff --git a/README.md b/README.md
index 2fef77b..16f7dd5 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ TDD(Today Delicious Delivery)는 이름 그대로 '오늘의 맛있는 배달'
| 분류 | 상세 |
|---------------------|----------------------------------------------------------------------------|
| **Back-End** | Java 21, Spring Boot 3.5.6, Spring Data JPA, Querydsl 7.0, Spring Security |
-| **Database** | PostgreSQL 18.0 |
+ **Database** | PostgreSQL 17.6 |
| **Build Tool** | Gradle |
| **Infra** | Docker compose, Github Actions(CI) |
| **open API** | Google GenAI API, Naver Map API |
@@ -76,7 +76,7 @@ docker exec -it tdd-db psql -U test -d tdd-db
## 1. ERD
-
+
## 2. 도메인 다이어그램
@@ -98,6 +98,124 @@ docker exec -it tdd-db psql -U test -d tdd-db
## 1. 도메인별 핵심 기능 상세 구현
+### (1) Order
+
+
+
+- Order 도메인
+ - DDD 원칙에 따라 주문의 상태 전이(`nextStatus`, `changeOrderStatus`) 로직을 엔티티 내부에 위치
+ - Order 와 Menu 의 다대다 관계를 OrderMenu 로 풀어내 도메인 응집도 강화
+ - Mapper 를 통해 Service 는 트랜잭션 중심의 조합 역할만 수행
+- getOrders(주문 목록 조회)
+ - QueryDSL을 이용한 동적 검색 (조건 null 시 필터 미적용)
+ - MANAGER, MASTER 권한 외 사용자는 본인 주문만 조회 (권한 검증)
+ - Page 처리로 ID 목록 우선 조회 후 fetch join으로 세부 데이터 일괄 로딩 (N+1 방지)
+- nextOrderStatus(주문 단계 변경)
+ - OWNER 가 가능한 주문 단계변경 메서드로 시스템의 규칙에 따라서만 변경이 가능하도록 nextStatus 로 동작
+ - 관리자 권한의 사용자들은 changeOrderStatus 를 통하여 임의 변경가능하도록 별도 API 구성
+
+- PageableHandlerMethodArgumentResolver
+ - 공통 페이징 정책을 일괄 적용하기 위해 WebMvcConfig 에 정책 등록
+ - 기본 정렬 기준(`createdAt DESC`)을 지정하여 일관된 정렬 정책 유지
+
+### (2) Authentication
+
+**회원가입**
+
+- username, password를 이용한 신규 사용자 등록
+- 회원가입 시점에서 권한(CUSTOMER, OWNER, MANAGER, MASTER) 설정 가능
+- 즉시 로그인 처리되어 Access Token(Header), Refresh Token(Cookie) 자동 발급
+- 비밀번호는 BCryptPasswordEncoder를 통해 암호화 저장
+
+**로그인**
+
+- username, password를 이용한 인증 처리
+- `PasswordEncoder.matches()`를 통한 비밀번호 검증
+- Access Token(Header), Refresh Token(Cookie) 발급
+
+**로그아웃**
+
+- 현재 사용 중인 Access Token과 Refresh Token을 블랙리스트에 추가
+- 로컬 캐시 기반 블랙리스트 관리로 빠른 토큰 무효화 처리
+- Refresh Token 쿠키 무효화
+
+**회원 탈퇴**
+
+- Soft Delete 방식으로 사용자 및 연관 데이터 삭제
+- 권한별 차등 삭제 로직:
+ - OWNER: 소유 가게 → 메뉴 → 리뷰 → 리뷰 댓글 등 연관 데이터 일괄 삭제
+ - CUSTOMER: 리뷰, 주문 등 개인 데이터 삭제
+- 탈퇴 후 자동 로그아웃 처리 (토큰 블랙리스트 추가)
+
+### (3) JWT 기반 인증 시스템
+
+**토큰 관리 전략**
+
+- Access Token: HTTP Authorization Header로 전달 (짧은 유효기간)
+- Refresh Token: HttpOnly Cookie로 전달 (긴 유효기간)
+- JWT 라이브러리: `jjwt` 사용
+
+**토큰 재발급 (RTR 기법 적용)**
+
+- Refresh Token Rotation (RTR) 기법을 통한 보안 강화
+ - 토큰 재발급 시 Refresh Token도 함께 재발급
+ - 기존 Refresh Token은 블랙리스트에 추가
+ - 탈취된 Refresh Token의 장기 사용 방지
+- Refresh Token의 최대 유효기간 유지
+ - 재발급 시에도 원본 Refresh Token의 만료 시간을 그대로 적용
+ - 무한 갱신 방지 및 주기적 재로그인 유도
+- Access Token, Refresh Token 모두 만료 시 재로그인 필요
+
+**인증 필터 처리**
+
+- `JwtAuthenticationFilter`:
+ - 요청 헤더 및 쿠키에서 토큰 추출
+ - 토큰 검증 및 사용자 인증 정보 생성
+ - SecurityContextHolder에 인증 정보 저장
+ - 토큰이 없는 경우 public URL로 판단
+- `JwtExceptionFilter`: JWT 필터에서 발생하는 예외 처리
+- `JwtTokenProvider`: 토큰 발급, 검증, 디코딩 담당
+
+### (3) Coupon
+
+
+
+**Coupon**
+
+- 가게 쿠폰 목록 조회
+ - 가게의 모든 쿠폰을 조회
+- Store 쿠폰 등록
+ - 반드시 `SCOPE`가 `STORE`여야 함
+ - `TYPE` 설정을 통해 고정된 할인(FIXED) 혹은 %할인(PERCENT) 선택 가능
+ - OWNER는 반드시 해당 가게 OWNER만 가능함
+- Master 쿠폰 등록
+ - 반드시 `SCOPE`가 `MASTER`여야 함
+ - 해당 기능은 MASTER만 가능
+- 쿠폰 수정
+ - 유저가 1회라도 발급받았다면, 쿠폰 수정은 불가
+ - OWNER는 반드시 해당 가게 OWNER만 가능함
+- 쿠폰 삭제
+ - 만료된 쿠폰은 스케쥴러를 통해 soft delete
+ - OWNER는 반드시 해당 가게 OWNER만 가능함
+
+**UserCoupon**
+
+- 내 쿠폰 목록 조회
+ - CouponStatus에 대하여 ACTIVE > USED/EXPIRED 순으로 정렬하되, 각 그룹에 대하여 생성일 순으로 정렬하여 조회
+- 쿠폰 발급
+ - 쿠폰 발급 기능
+ - exist 설정을 통해 중복 발급 방지
+ - 사용자가 몰려 해당 기능을 수행할 경우를 대비해야 함(동시성 문제)
+
+**기타**
+
+- 모든 API에 대하여 @PreAuthorize를 통해 허용된 권한만 접근 가능하도록 설정
+- SRT 원칙을 지키며 각 클래스가 명확한 책임을 가지도록 메서드 단위를 최소화하여 개발
+- 추가 로직
+ - UserCoupon 만료/만료된 Coupon 삭제 처리를 하나의 트랜잭션 작업으로 묶어서 매일 자정 스케쥴링
+ - 만료된지 7일 된 UserCoupon에 대한 삭제 처리 스케쥴링
+ - Order에서 주문 생성 시 쿠폰을 적용할 수 있는 로직 추가(최소 주문 금액에 맞는지 확인 및 총 결제금액 계산까지 확장 가능)
+
## 2. 트러블슈팅
### (1) CustomPageableResolver Bean 등록 미적용 문제
@@ -354,11 +472,11 @@ class StoreRepositoryTest extends RepositoryTest {
# 팀원 소개
-| 팀원 | 깃허브 | 역할 |
-|-----|--------------------------------------------|-----------------------------------------------|
-| 박성민 | [@dnjsals45](https://github.com/dnjsals45) | Auth, Payment 도메인 개발 및 테스트코드 작성 |
-| 김민수 | [@Doritosch](https://github.com/Doritosch) | User, AI, Address 도메인 개발 및 테스코드 작성 |
-| 김채연 | [@yeon-22k](https://github.com/yeon-22k) | Menu, Coupon 도메인 개발 및 테스트코드 작성 |
-| 박주찬 | [@p990805](https://github.com/p990805) | Review, ReviewReply, Cart 도메인 개발 및 테스트코드 작성 |
-| 변영재 | [@bbangjae](https://github.com/bbangjae) | Store, Point 도메인 개발 및 테스트코드 작성 |
-| 송의현 | [@yawning5](https://github.com/yawning5) | Order, OrderMenu 도메인 개발 및 테스트코드 작성, 페이징 정책 적용 |
\ No newline at end of file
+| 팀원 | 깃허브 | 역할 |
+|-----|--------------------------------------------|----------------------------------------------------------------------------------|
+| 박성민 | [@dnjsals45](https://github.com/dnjsals45) | Auth, Payment 도메인 개발 및 테스트코드 작성 |
+| 김민수 | [@Doritosch](https://github.com/Doritosch) | AI 메뉴 소개 생성, 주소 및 유저 도메인 개발 및 테스트코드 작성 |
+| 김채연 | [@yeon-22k](https://github.com/yeon-22k) | Menu, Coupon 도메인 개발 및 테스트코드 작성, Coupon 스케쥴링 만료/삭제 구현, ReadMe 통합 작업 |
+| 박주찬 | [@p990805](https://github.com/p990805) | Review, ReviewReply, Cart 도메인 개발 및 테스트코드 작성 |
+| 변영재 | [@bbangjae](https://github.com/bbangjae) | Store 도메인 개발 및 테스트 코드 작성, Point 로직(AOP 기반), AuditorAware를 통한 생성자/수정자 자동 관리 기능 구현 |
+| 송의현 | [@yawning5](https://github.com/yawning5) | Order, OrderMenu 도메인 개발 및 테스트코드 작성, 페이징 정책 적용 |
\ No newline at end of file
diff --git a/api-docs.md b/api-docs.md
index 070bb23..8b2bd97 100644
--- a/api-docs.md
+++ b/api-docs.md
@@ -2,53 +2,1314 @@
## 도메인 개요
-- **Address**: 가게와 사용자 주소를 등록 및 관리합니다.
-- **AI**: Google GenAI 를 활용한 글 생성을 제공합니다.
-- **Auth**:사용자의 회원가입, 로그인, 토큰 발급 및 검증을 포함한 인증과 인가 로직을 담당합니다.
-- **Cart**:장바구니는 주문 전에 사용자가 선택한 메뉴 아이템을 임시로 저장하고 관리합니다.
-- **Coupon**: MASTER 및 STORE 쿠폰 발행, 수정, 삭제, 조회와 사용자의 쿠폰 발급 및 상태를 관리합니다.
-- **Menu**: 가게별 메뉴의 등록, 수정, 삭제, 조회를 관리하며 메뉴의 노출상태와 가격 등의 정보를 제공합니다.
-- **Order**: 사용자의 주문 요청부터 상태 변경까지의 주문 처리 전 과정을 제어합니다.
-- **OrderMenu**: 개별 주문에 포함된 메뉴 항목을 관리하며, 주문-메뉴 간 다대다 관계를 매핑하는 역할을 수행합니다.
-- **Payment**: 주문에 대한 결제 정보를 관리하며, 결제 요청/승인/취소 등의 프로세스를 처리합니다.
-- **Review**: 고객의 리뷰 작성, 수정, 삭제와 점주의 답글 기능을 지원하여 양방향 피드백 시스템을 제공합니다.
-- **Store**: 가게의 등록, 수정, 검색, 상세조회 기능을 담당하며 점주와 고객 간의 매장 정보 접근 제어를 수행합니다.
-- **User**: 사용자의 권한(Role) 및 프로필 정보를 관리하며 이용자 유형에 따른 접근 가능 리소스를 제한합니다.
+| 도메인 | 설명 |
+|---------------|-------------------------------------------------------------|
+| **Address** | 가게와 사용자 주소를 등록 및 관리합니다. |
+| **AI** | Google GenAI 를 활용한 글 생성을 제공합니다. |
+| **Auth** | 사용자의 회원가입, 로그인, 토큰 발급 및 검증을 포함한 인증과 인가 로직을 담당합니다. |
+| **Cart** | 장바구니는 주문 전에 사용자가 선택한 메뉴 아이템을 임시로 저장하고 관리합니다. |
+| **Coupon** | MASTER 및 STORE 쿠폰 발행, 수정, 삭제, 조회와 사용자의 쿠폰 발급 및 상태를 관리합니다. |
+| **Menu** | 가게별 메뉴의 등록, 수정, 삭제, 조회를 관리하며 메뉴의 노출상태와 가격 등의 정보를 제공합니다. |
+| **Order** | 사용자의 주문 요청부터 상태 변경까지의 주문 처리 전 과정을 제어합니다. |
+| **OrderMenu** | 개별 주문에 포함된 메뉴 항목을 관리하며, 주문-메뉴 간 다대다 관계를 매핑하는 역할을 수행합니다. |
+| **Payment** | 주문에 대한 결제 정보를 관리하며, 결제 요청/승인/취소 등의 프로세스를 처리합니다. |
+| **Review** | 고객의 리뷰 작성, 수정, 삭제와 점주의 답글 기능을 지원하여 양방향 피드백 시스템을 제공합니다. |
+| **Store** | 가게의 등록, 수정, 검색, 상세조회 기능을 담당하며 점주와 고객 간의 매장 정보 접근 제어를 수행합니다. |
+| **User** | 사용자의 권한(Role) 및 프로필 정보를 관리하며 이용자 유형에 따른 접근 가능 리소스를 제한합니다. |
+
+## API 명세서




-## API 명세서
-
-
-| 도메인 | 기능 | 메서드 | URI | Request | Response |
-
-
-| Address | 가게 주소 등록 | 메서드 | /v1/address/store |
-{
+
+ | 도메인 | 기능 | 메서드 | URI | Request | Response |
+
+
+ | Address | 가게 주소 등록 | POST | /v1/address/store |
+ {
"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
-“detailAddress”: “101동”,
-"latitude": "127.1052160",
+"detailAddress": "101동",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+}
|
+ {
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"latitude": "127.105216",
"longitude": "37.3595033"
+}
|
+
+
+
+ | Address | 가게 주소 수정 | PATCH | /v1/address/store/{storeId} |
+
+{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"latitude": "126.8896815",
+"longitude": "37.505573"
}
|
-
+
+{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"latitude": "126.8896815",
+"longitude": "37.505573"
+}
+
|
+
|
+
+
+ | Address | 가게 주소 삭제 | DELETE | /v1/address/store/{storeId} |
+ |
+ |
+
+
+
+ | Address | 회원 주소 등록 | POST | /v1/address/user |
+ {
+ "roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+ "jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+ "detailAddress": "101동",
+ "alias": "집",
+ "latitude": "127.1052160",
+ "longitude": "37.3595033"
+}
|
+
+{
+"uuid": "2c92e6c7-6906-4f06-b34e-124b51210520",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"alias": "우리집",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+}
+
|
+
+
+
+ | Address | 전체 회원 주소 목록 조회 | GET | /v1/address/user/all?page=0&size=10 |
+ |
+
+{
+"content": [
+{
+"uuid": "90553608-0f86-441f-811f-8dc7abf70e9b",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"latitude": "126.8896815",
+"longitude": "37.505573"
+}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 1,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 1,
+"empty": false
+}
+
|
+
-| Address | 가게 주소 수정 | 메서드 | /v1/address/store/{storeId} |
-{
+ | Address | 회원 주소 조회 | GET | /v1/address/user |
+ |
+
+[
+{
+"uuid": "d06857dd-0090-4821-a1a3-ffbb662b40c6",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"alias": "우리집",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+},
+{
+"uuid": "6174753c-6b24-43fe-b526-754825ff1b1f",
+"jibunAddress": "서울특별시 구로구 구로동 5-1",
+"roadAddress": "서울특별시 구로구 새말로 89",
+"detailAddress": "2층",
+"alias": "회사",
+"latitude": "126.8896815",
+"longitude": "37.505573"
+}
+]
+
|
+
|
+
+
+ | Address | 회원 주소 수정 | PATCH | /v1/address/user/{addressId} |
+ {
+ "roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+ "jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
+ "detailAddress": "102동",
+ "alias": "회사",
+ "latitude": "127.1052160",
+ "longitude": "37.3595033"
+}
+{
+"uuid": "d06857dd-0090-4821-a1a3-ffbb662b40c6",
+"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
+"detailAddress": "101동",
+"alias": "회사2",
+"latitude": "127.105216",
+"longitude": "37.3595033"
+}
|
+ |
+
+
+
+ | Address | 회원 주소 삭제 | DELETE | /v1/address/user/{addressId} |
+ |
+ |
+
+
+
+ | Address | API 주소 조회 | GET | /v1/address?query={address} |
+ |
+
+{
+"content": [
+{
"jibunAddress": "경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리",
-“detailAddress”: “101동”,
+"roadAddress": "경기도 성남시 분당구 불정로 6 NAVER그린팩토리",
"latitude": "127.1052160",
"longitude": "37.3595033"
}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 1,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 1,
+"empty": false
+}
|
+
+
+
+
+
+ | Auth | 회원가입 | POST | /v1/auth/signup |
+ {
+ "username": "username",
+ "password": "password"
+}
|
+ {
+ "userId": 1
+}
|
+
+
+
+ | Auth | 유저네임 중복확인 | GET | /v1/auth/exists?username={username} |
+ |
+ {
+ "exists": true
+}
|
+
+
+
+ | Auth | 로그인 | POST | /v1/auth/login |
+ {
+ "username": "username",
+ "password": "password"
+}
|
+ {
+ "userId": 1
+}
|
+
+
+
+ | Auth | 로그아웃 | POST | /v1/auth/logout |
+ |
+ |
+
+
+
+ | Auth | 회원 탈퇴 | DELETE | /v1/auth/withdrawal |
+ |
+ |
+
+
+
+ | Auth | 토큰 재발급 | POST | /v1/auth/token/reissue |
+ |
+ |
+
+
+
+
+ | Cart | 장바구니 조회 | GET | /v1/cart |
+ |
+ {
+ "cartId": "UUID",
+ "userId": 1,
+ "items": [
+ {
+ "cartItemId": "UUID",
+ "menuId": "UUID",
+ "menuName": "김치찌개",
+ "price": 8000,
+ "quantity": 2,
+ "totalPrice": 16000,
+ "storeId": "UUID",
+ "storeName": "맛있는식당"
+ }
+ ],
+ "totalPrice": 16000,
+ "storeId": "UUID"
+}
|
+
+
+
+ | Cart | 장바구니에 아이템 추가 | POST | /v1/cart/items |
+ {
+ "menuId": "UUID",
+ "quantity": 2
+}
|
+ {
+ "cartId": "UUID",
+ "userId": 1,
+ "items": [
+ {
+ "cartItemId": "UUID",
+ "menuId": "UUID",
+ "menuName": "김치찌개",
+ "price": 8000,
+ "quantity": 2,
+ "totalPrice": 16000,
+ "storeId": "UUID",
+ "storeName": "맛있는식당"
+ }
+ ],
+ "totalPrice": 16000,
+ "storeId": "UUID"
+}
|
+
+
+
+ | Cart | 장바구니 아이템 수량 수정 | PATCH | /v1/cart/items/{cartItemId}?quantity={quantity} |
+ /v1/cart/items/{cartItemId}?quantity=5
|
+ {
+ "cartId": "UUID",
+ "userId": 1,
+ "items": [],
+ "totalPrice": 0,
+ "storeId": null
+}
|
+
+
+
+ | Cart | 장바구니 아이템 삭제 | DELETE | /v1/cart/items/{cartItemId} |
+ /v1/cart/items/{cartItemId}
|
+
+{
+"cartId": “UUID",
+"userId": 1,
+"items": [],
+"totalPrice": 0,
+"storeId": null
+}
|
+
+
+
+ | Cart | 장바구니 비우기 | DELETE | /v1/cart |
+ |
+ |
+
+
+
+
+ | Coupon | 매장 쿠폰 목록 조회 | GET | /v1/coupon/list/{storeId} |
+ |
+ [
+ {
+ "couponId": "9f2d9a2e-1b4a-45a3-9b7f-2f9e6b2d2f00",
+ "name": "주말 10% 할인",
+ "type": "PERCENT",
+ "scope": "STORE",
+ "discountValue": 10,
+ "minOrderPrice": 15000,
+ "quantity": 500,
+ "issuedCount": 132,
+ "expiredAt": "2025-12-31T23:59:59"
+ },
+ {
+ "couponId": "74a7c6c5-8a18-4e1e-9a3b-0c2b4f8f9c11",
+ "name": "3천원 즉시할인",
+ "type": "FIXED",
+ "scope": "STORE",
+ "discountValue": 3000,
+ "minOrderPrice": 12000,
+ "quantity": 300,
+ "issuedCount": 98,
+ "expiredAt": "2025-11-30T23:59:59"
+ }
+]
|
+
+
+
+ | Coupon | 내 쿠폰 목록 조회 | GET | /v1/coupon/my/list |
+ |
+ [
+ {
+ "userCouponId": "a2b0a5ef-2d9c-4c6d-9b0e-1a2b3c4d5e6f",
+ "userId": 101,
+ "couponId": "9f2d9a2e-1b4a-45a3-9b7f-2f9e6b2d2f00",
+ "couponStatus": "ACTIVE"
+ },
+ {
+ "userCouponId": "e3c1d2f4-5678-49ab-9cde-0123456789ab",
+ "userId": 101,
+ "couponId": "74a7c6c5-8a18-4e1e-9a3b-0c2b4f8f9c11",
+ "couponStatus": "USED"
+ }
+]
|
+
+
+
+ | Coupon | Store 쿠폰 등록 | POST | /v1/coupon/{storeId} |
+ {
+ "name": "2000원 할인",
+ "type": "FIXED",
+ "scope": "STORE",
+ "discountValue": 2000,
+ "minOrderPrice": 10000,
+ "quantity": 1000,
+ "expiredAt": "2025-12-31T23:59:59"
+}
|
+ {
+ "couponId": "2c3b4a5d-6e7f-4a8b-9c0d-1e2f3a4b5c6d",
+ "name": "2000원 할인",
+ "type": "FIXED",
+ "scope": "STORE",
+ "discountValue": 2000,
+ "minOrderPrice": 10000,
+ "quantity": 1000,
+ "issuedCount": 0,
+ "expiredAt": "2025-12-31T23:59:59"
+}
|
+
+
+
+ | Coupon | MASTER 쿠폰 등록 | POST | /v1/coupon/{couponId} |
+ {
+ "name": "전매장 5천원 할인",
+ "type": "FIXED",
+ "scope": "MASTER",
+ "discountValue": 5000,
+ "minOrderPrice": 20000,
+ "quantity": 10000,
+ "expiredAt": "2026-01-31T23:59:59"
+}
|
+ {
+ "couponId": "d4f1a2b3-c4d5-4e6f-8a90-b1c2d3e4f5a6",
+ "name": "전매장 5천원 할인",
+ "type": "FIXED",
+ "scope": "MASTER",
+ "discountValue": 5000,
+ "minOrderPrice": 20000,
+ "quantity": 10000,
+ "issuedCount": 0,
+ "expiredAt": "2026-01-31T23:59:59"
+}
|
+
+
+
+ | Coupon | Master 유저 쿠폰 등록 | POST | /v1/coupon/{storeId} |
+ |
+ {
+ "userCouponId": "ab12cd34-ef56-7890-ab12-cd34ef567890",
+ "userId": 101,
+ "couponId": "d4f1a2b3-c4d5-4e6f-8a90-b1c2d3e4f5a6",
+ "couponStatus": "ACTIVE"
+}
|
+
+
+
+ | Coupon | 쿠폰 수정 | PATCH | /v1/coupon/{couponId} |
+ {
+ "name": "2000원 할인",
+ "type": "FIXED",
+ "scope": "STORE",
+ "discountValue": 2000,
+ "minOrderPrice": 10000,
+ "quantity": 1000,
+ "expiredAt": "2025-12-31T23:59:59"
+}
|
+ |
+
+
+
+ | Coupon | 쿠폰 삭제 | DELETE | /v1/coupon/{couponId} |
+ |
+ |
+
+
+
+
+
+ | Menu | 메뉴 조회(목록) | GET | /v1/store/{storeId}/menu |
+ |
+ [
+ {
+ "menuId": "550e8400-e29b-41d4-a716-446655440000",
+ "name": "치킨버거",
+ "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+ "price": 8900,
+ "imageUrl": "https://example.com/images/chicken-burger.jpg",
+ "isHidden": false
+ },
+ {
+ "menuId": "551e8400-e29b-41d4-a716-446655440001",
+ "name": "새우버거",
+ "description": "통통한 새우와 타르타르소스의 조화",
+ "price": 9500,
+ "imageUrl": "https://example.com/images/shrimp-burger.jpg",
+ "isHidden": false
+ }
+]
|
+
+
+
+ | Menu | 메뉴 조회(개별) | GET | /v1/store/{storeId}/menu/{menuId} |
+ |
+ {
+ "menuId": "550e8400-e29b-41d4-a716-446655440000",
+ "storeId": "660e8400-e29b-41d4-a716-446655440000",
+ "name": "치킨버거",
+ "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+ "price": 8900,
+ "imageUrl": "https://example.com/images/chicken-burger.jpg",
+ "isHidden": false
+}
|
+
+
+
+ | Menu | 메뉴 등록 | POST | /v1/store/{storeId}/menu |
+ {
+ "name": "치킨버거",
+ "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+ "price": 8900,
+ "imageUrl": "https://example.com/images/chicken-burger.jpg",
+ "isHidden": false
+}
|
+ {
+ "menuId": "550e8400-e29b-41d4-a716-446655440000",
+ "restaurantId": "660e8400-e29b-41d4-a716-446655440000",
+ "name": "치킨버거",
+ "description": "바삭한 치킨과 신선한 야채가 들어간 버거",
+ "price": 8900,
+ "imageUrl": "https://example.com/images/chicken-burger.jpg",
+ "isHidden": false,
+ "createdAt": "2025-09-29T12:00:00.000000000"
+}
|
+
+
+
+ | Menu | 메뉴 수정 | PATCH | /v1/store/{storeId}/menu/{menuId} |
+ { "name": "수정버거" }
|
+ |
+
+
+
+ | Menu | 메뉴 상태 변경 | PATCH | /v1/store/{storeId}/menu/{menuId}/status |
+ { "isHidden": true }
|
+ |
+
+
+
+ | Menu | 메뉴 삭제 | DELETE | /v1/store/{storeId}/menu/{menuId} |
+ |
+ |
+
+
+
+
+ | Order | 주문 목록 조회 | GET | /v1/orders?from={startDate}&to={endDate}&status={status}&userId={userId}&storeId={storeId}&sort={sort}&page={page}&size={size} |
+ |
+ {
+ "totalElements": 0,
+ "totalPages": 0,
+ "size": 0,
+ "content": [
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "customerName": "string",
+ "storeName": "string",
+ "price": 0,
+ "address": "string",
+ "orderMenuList": [
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "name": "string",
+ "price": 0,
+ "quantity": 0
+ }
+ ],
+ "createdAt": "2025-10-17T08:14:04.380Z",
+ "orderStatus": "CANCELLED"
+ }
+ ],
+ "number": 0,
+ "sort": {
+ "empty": true,
+ "sorted": true,
+ "unsorted": true
+ },
+ "first": true,
+ "last": true,
+ "numberOfElements": 0,
+ "pageable": {
+ "offset": 0,
+ "sort": {
+ "empty": true,
+ "sorted": true,
+ "unsorted": true
+ }
+ }
+}
|
+
+
+
+ | Order | 주문 조회 | GET | /v1/orders/{orderId} |
+ |
+ {
+ "id": 1002,
+ "customerName": "c",
+ "storeName": "d",
+ "price": 45000,
+ "address": "부산시 어떤가 456",
+ "orderMenuList": [
+ {
+ "id": "UUID",
+ "name": "탕수육",
+ "price": 20000,
+ "quantity": 1
+ },
+ {
+ "id": "UUID",
+ "name": "깐풍기",
+ "price": 25000,
+ "quantity": 2
+ }
+ ],
+ "createdAt": "2025-09-29T12:34:56",
+ "orderStatus": "DELIVERED"
+}
|
+
+
+
+ | Order | 주문 등록 | POST | /v1/orders |
+ {
+ "address": "부산시 어떤가 456",
+ "customerName": "c",
+ "storeId": "UUID",
+ "storeName": "d",
+ "price": 45000,
+ "menu": [
+ {
+ "id": "UUID",
+ "name": "탕수육",
+ "price": 20000,
+ "quantity": 1
+ },
+ {
+ "id": "UUID",
+ "name": "깐풍기",
+ "price": 25000,
+ "quantity": 2
+ }
+ ]
+}
|
+ {
+ "id": 1002,
+ "customerName": "c",
+ "storeName": "d",
+ "price": 45000,
+ "address": "부산시 어떤가 456",
+ "orderMenuList": [
+ {
+ "id": "UUID",
+ "name": "탕수육",
+ "price": 20000,
+ "quantity": 1
+ },
+ {
+ "id": "UUID",
+ "name": "깐풍기",
+ "price": 25000,
+ "quantity": 2
+ }
+ ],
+ "createdAt": "2025-09-29T12:34:56",
+ "orderStatus": "DELIVERED"
+}
|
+
+
+
+ | Order | 주문 상태 변경(관리자) | PATCH | /v1/orders/{orderId} |
+ { "status": "DELIVERED" }
|
+ {
+"id": 1002,
+"customerName": "c",
+"storeName": "d",
+"price": 45000,
+"address": "부산시 어딘가 456",
+"orderMenuList": [
+{ “id”: UUID, "name": "탕수육", "price": 20000, "quantity": 1 },
+{”id”: UUID, "name": "깐풍기", "price": 25000, "quantity": 2 }
+],
+"createdAt": "2025-09-29T12:34:56",
+”oderStatus”: “DELIVERED”
+}
|
+
+
+
+ | Order | 주문 상태 변경(가게주인, 관리자) | PATCH | /orderId/next-status |
+ |
+ {
+"id": 1002,
+"customerName": "c",
+"storeName": "d",
+"price": 45000,
+"address": "부산시 어딘가 456",
+"orderMenuList": [
+{ “id”: UUID, "name": "탕수육", "price": 20000, "quantity": 1 },
+{”id”: UUID, "name": "깐풍기", "price": 25000, "quantity": 2 }
+],
+"createdAt": "2025-09-29T12:34:56",
+”oderStatus”: “DELIVERED”
+}
+
|
+
+
+
+ | Order | 주문 취소 | PATCH | /cancel/{orderId} |
+ |
+ {
+ "id": 1002,
+ "customerName": "c",
+ "storeName": "d",
+ "price": 45000,
+ "address": "부산시 어떤가 456",
+ "orderMenuList": [
+ {
+ "id": "UUID",
+ "name": "탕수육",
+ "price": 20000,
+ "quantity": 1
+ },
+ {
+ "id": "UUID",
+ "name": "깐풍기",
+ "price": 25000,
+ "quantity": 2
+ }
+ ],
+ "createdAt": "2025-09-29T12:34:56",
+ "orderStatus": "CANCELLED"
+}
|
+
+
+
+
+ | Payment | 결제내역 조회 | GET | /v1/payments?page=1&size=10&orderBy={정렬조건}&keyword={검색조건} |
+ |
+ {
+ “content”: [
+ {
+ “id”: UUID
+ “paymentNumber”: “12312312”,
+ “status”: “결제 성공”,
+ “storeName”: “음식점1”,
+ “price”: 10000
+ },
+ …
+ ],
+ "pageable": {
+ "pageNumber": 0,
+ "pageSize": 10,
+ "sort": {
+ "empty": false,
+ "sorted": true,
+ "unsorted": false
+ },
+ "offset": 0,
+ "paged": true,
+ "unpaged": false
+},
+ "totalPages": 1,
+ "last": true,
+ "totalElements": 8,
+ "size": 10,
+ "number": 0,
+ "sort": {
+ "empty": false,
+ "sorted": true,
+ "unsorted": false
+ },
+ "first": true,
+ "numberOfElements": 8,
+ "empty": false
+}
|
+
+
+
+ | Payment | 결제내역 상세 조회 | GET | /v1/payments/{paymentId} |
+ |
+ {
+ “paymentNumber”: “1q2w3e4r!”,
+ “price”: 10000,
+ “cardCompany”: “XX카드”,
+ “cardNumber”: “1111 **** **** 4444”,
+ “processedAt”: "2025-09-29T10:15:00"
+ “restaurant”: {
+ “id”: 1,
+ “storeName”: “음식점”,
+ },
+ “orderItem” [
+ {
+ “id”: 5,
+ “menuName”: “음식1”,
+ “quantity”: 1
+ “price”: 5000,
+ “totalPrice”: 5000
+ },
+ …
+ ]
+}
|
+
+
+
+ | Payment | 결제 상태 변경 | PATCH | /v1/payments/status/{paymentId} |
+ {
+ “status”: “COMPLETED”
+}
|
+ |
+
+
+
+ | Payment | 결제 요청 | POST | /v1/payments |
+ {
+ “orderId”: UUID,
+ “cardCompany”: “SAMSUNG”,
+ “cardNumber”: “1111 **** **** 4444”
+}
|
+ |
+
+
+
+
+ | Review | 리뷰 등록 | POST | /v1/reviews/order/{orderId} |
+ {
+ "content": "리뷰 내용",
+ "storeId": 1,
+ "rating": 3,
+ "photos": "abc.png"
+}
|
+ {
+ "reviewId": 123,
+ "storeId": 1,
+ "content": "너무 맛있어요!",
+ "rating": 3,
+ "userId": "user01",
+ "photos": "abc.png",
+ "createdAt": "2025-09-29T10:15:00",
+ "modifiedAt": "2025-09-29T10:20:00"
+}
|
+
+
+
+ | Review | 리뷰 수정 | PATCH | /v1/reviews/{reviewId} |
+ {
+ "content": "리뷰 내용",
+ "rating": 3,
+ "photos": "abc.png"
+}
|
+ {
+ "reviewId": 123,
+ "storeId": 1,
+ "content": "너무 맛있어요!",
+ "rating": 3,
+ "userId": "user01",
+ "photos": "abc.png",
+ "createdAt": "2025-09-29T10:15:00",
+ "modifiedAt": "2025-09-29T10:20:00"
+}
|
+
+
+
+ | Review | 리뷰 개별 조회 | GET | /v1/reviews/{reviewId} |
+ |
+ {
+ "reviewId": 123,
+ "storeId": 1,
+ "content": "너무 맛있어요!",
+ "rating": 3,
+ "userId": "user01",
+ "photos": "abc.png",
+ "createdAt": "2025-09-29T10:15:00",
+ "modifiedAt": "2025-09-29T10:20:00"
+}
|
+
+
+
+ | Review | 리뷰 목록 조회 | GET | /v1/reviews/{storeId}?page=1&size=10 |
+ |
+ {
+ "reviews": [
+ {
+ "reviewId": 101,
+ "storeId": 1,
+ "content": "음식이 맛있어요!",
+ "rating": 3,
+ "userId": "user01",
+ "photos": "abc.png",
+ "createdAt": "2025-09-25T14:30:00",
+ "modifiedAt": "2025-09-29T10:20:00",
+ "reply": {
+ "content": "이용해주셔서 감사합니다"
+ }
+ },
+ {
+ "reviewId": 102,
+ "storeId": 1,
+ "content": "서비스가 친절했어요.",
+ "rating": 3,
+ "userId": "user02",
+ "photos": "abc.png",
+ "createdAt": "2025-09-26T16:45:00",
+ "modifiedAt": "2025-09-29T10:20:00",
+ "reply": {
+ "content": "다음에도 오세요"
+ }
+ }
+ ],
+ "pageInfo": {
+ "page": 1,
+ "size": 10,
+ "totalElements": 52,
+ "totalPages": 6,
+ "hasNext": true
+ }
+}
|
+
+
+
+ | Review | 리뷰 삭제 | DELETE | /v1/reviews/{reviewId} |
+ |
+ {
+ "status": 200,
+ "message": "리뷰가 성공적으로 삭제되었습니다."
+}
|
+
+
+
+ | Review | 리뷰 답글 등록 | POST | /v1/reviews/{reviewId}/reply |
+ { "content": "사장님 답글 내용" }
|
+ {
+ "replyId": 88,
+ "reviewId": 123,
+ "storeId": 1,
+ "ownerId": 1,
+ "content": "리뷰 감사합니다! 또 이용해주세요",
+ "createdAt": "2025-09-29T10:15:00",
+ "modifiedAt": "2025-09-29T10:20:00"
+}
|
+
+
+
+ | Review | 리뷰 답글 수정 | PATCH | /v1/reviews/{reviewId}/reply |
+ { "content": "사장님 수정 답글 내용" }
|
+ {
+ "replyId": 88,
+ "reviewId": 123,
+ "storeId": 1,
+ "ownerId": 1,
+ "content": "리뷰 감사합니다! 또 이용해주세요",
+ "createdAt": "2025-09-29T10:15:00",
+ "modifiedAt": "2025-09-29T10:20:00"
+}
|
+
+
+
+ | Review | 리뷰 답글 삭제 | DELETE | /v1/reviews/{reviewId}/reply |
+ |
+ |
+
+
+ | Review | 리뷰 목록 조회(사장님 답글 추가 시) | GET | /v1/reviews/{storeId}?page=1&size=10 |
+ |
+ {
+ "reviews": [
+ {
+ "reviewId": 101,
+ “storeId”: 1,
+ "content": "음식이 맛있어요!",
+ “rating”: 3,
+ "userId": "user01",
+ “photos”: “abc.png”,
+ "createdAt": "2025-09-25T14:30:00",
+ "modifiedAt": "2025-09-29T10:20:00”,
+ “reply”: {
+ “conetent”: 이용해주셔서 감사합니다
+ }
+},
+{
+"reviewId": 102,
+“storeId”: 1,
+"content": "서비스가 친절했어요.",
+“rating”: 3,
+"userId": "user02",
+“photos”: “abc.png”,
+"createdAt": "2025-09-26T16:45:00",
+"modifiedAt": "2025-09-29T10:20:00”
+“reply”: {
+“conetent”: 다음에 또 오세요
+}
+}
+],
+"pageInfo": {
+"page": 1,
+"size": 10,
+"totalElements": 52,
+"totalPages": 6,
+"hasNext": true
+}
+}
|
+
+
+
+ | Store | 음식점 검색 | GET | /v1/search?name={keyword}&category={keyword}&description={keyword}&sort=createdAt,desc&page=2&size=30 |
+ { "String": "keyword" }
|
+ {
+ "content": [
+ {
+ "id": "d9756dfd-99e9-480d-a7f7-4e38b00d5163",
+ "name": "맛스터치 강남점",
+ "ownerName": "jae",
+ "category": "CHICKEN",
+ "description": "강남 최고 치킨버거",
+ "imageUrl": "https://example.com/images/chicken-burger.jpg",
+ "avgRating": 0,
+ "reviewCount": 0,
+ "orderCount": null,
+ "menus": [
+ {
+ "menuId": "bf03b171-1ccb-4173-80b6-e890a176f69b2",
+ "name": "싸이버거",
+ "description": "맘스터치 대표 메뉴",
+ "price": 5500,
+ "imageUrl": null,
+ "isHidden": null
+ },
+ {
+ "menuId": "704c5850-d379-454d-8f66-6fdba7e5e22",
+ "name": "감자튀김",
+ "description": "바삭한 감자튀김",
+ "price": 2500,
+ "imageUrl": null,
+ "isHidden": null
+ }
+ ]
+ }
+ ]
+}
|
+
+
+
+ | Store | 음식점 상세 조회 | GET | /v1/store/{storeId} |
+ { "storeId": "storeId" }
|
+ {
+ "status": 200,
+ "message": "음식점 상세 조회에 성공했습니다.",
+ "data": {
+ "name": "name",
+ "category": "category",
+ "description": "description",
+ "address": "address",
+ "imageUrl": "imageUrl",
+ "avgRating": 4.5,
+ "reviewCount": 500
+ }
+}
|
+
+
+
+ | Store | 음식점 등록 | POST | /v1/store |
+ {
+ "name": "name",
+ "category": "category",
+ "description": "description",
+ "address": "address",
+ "imageUrl": "imageUrl"
+}
|
+ |
+
+
+
+ | Store | 음식점 수정 | PATCH | /v1/store/{storeId} |
+ {
+ "storeId": "storeId",
+ "name": "newName",
+ "category": "newCategory",
+ "description": "newDescription",
+ "address": "newAddress",
+ "imageUrl": "newImageUrl"
+}
|
+ |
+
+
+
+ | Store | 음식점 삭제 | DELETE | /v1/store/{storeId} |
+ {
+”storeId”:”storeId”
+} |
+ |
+
+
+
+ | User | 회원 정보 조회 | GET | /v1/users/{userId} |
+ |
+ {
+ "userId": "userId",
+ "username": "username"
+}
|
+
+
+ | User | 회원 닉네임 수정 | PATCH | /v1/users/{userId}/nickname |
+ {
+ "nickname": "nickname"
+}
|
+ {
+"id": 1,
+"username": "test01",
+"password": "$2a$10$FYt/selrPyrJDEBzyjIyAu3z21285Kzjxh10GuLO9NYQDod7WkTOC",
+"nickname": "테스트입니다",
+"authority": "고객"
+} |
+
+
+ | User | 회원 비밀번호 수정 | PATCH | /v1/users/{userId}/password |
+ {
+ "password": "password"
+}
|
+ {
+"id": 1,
+"username": "test01",
+"password": "$2a$10$OZnji3urkIVAufCRiwIu8e4t.JWZUZMSe.dM4qAu9r95A7qzkpFN2",
+"nickname": "테스트입니다",
+"authority": "고객"
+}
|
+
+
+ | User | 회원 리뷰 목록 조회 | GET | /v1/users/{userId}/reviews |
+ |
+ {
+ "data": [
+ {
+ "page": 1,
+ "size": 1,
+ "reviews": {
+ "reviewId": "reviewId",
+ "content": "content",
+ "createdAt": "2025-09-29T10:15:00",
+ "storeName": "storeName"
+ }
+ }
+ ]
+}
|
+
+
+ | User | 회원 목록 조회 | GET | /v1/users |
+ |
+ {
+"users": {
+"content": [
+{
+"id": 5,
+"username": "master1",
+"password": "$2a$10$yr4rLG.uvjLECZLn1AMP..5o1tRO0gzhEvf5a7LiZ5ImS9Mx4YW2y",
+"nickname": "owner",
+"authority": "최종 관리자"
+},
+…
+{
+"id": 1,
+"username": "test01",
+"password": "$2a$10$OZnji3urkIVAufCRiwIu8e4t.JWZUZMSe.dM4qAu9r95A7qzkpFN2",
+"nickname": "테스트입니다",
+"authority": "고객"
+}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 5,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 5,
+"empty": false
+}
+}
|
+
+
+ | User | 회원 주문 목록 조회 | GET | /v1/users/{userId}/orders |
+ |
+ {
+"content": [
+{
+"id": "072979c1-8eaf-49c1-a36c-306c5c0792d7",
+"customerName": "test01",
+"storeName": "한식당",
+"price": 13000,
+"address": "string",
+"orderMenuList": [
+{
+"id": "56167b37-e9b1-4855-b1c9-f5ea54506fa8",
+"name": "칼국수",
+"price": 5000,
+"quantity": 1
+},
+{
+"id": "687e2748-1860-4c0a-bdd6-c77b0275f28b",
+"name": "김밥",
+"price": 2000,
+"quantity": 4
+}
+],
+"createdAt": "2025-10-18T13:34:46.945571",
+"orderStatus": "PENDING"
+},
+{
+"id": "c39792a8-60bd-4075-841e-6233bfe5c731",
+"customerName": "test01",
+"storeName": "한식당",
+"price": 15000,
+"address": "string",
+"orderMenuList": [
+{
+"id": "8093a169-80e7-43a5-8fd2-9a997c200f4f",
+"name": "칼국수",
+"price": 5000,
+"quantity": 3
+}
+],
+"createdAt": "2025-10-18T13:33:40.164406",
+"orderStatus": "PENDING"
+}
+],
+"pageable": {
+"pageNumber": 0,
+"pageSize": 10,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"offset": 0,
+"unpaged": false,
+"paged": true
+},
+"last": true,
+"totalPages": 1,
+"totalElements": 2,
+"first": true,
+"size": 10,
+"number": 0,
+"sort": {
+"empty": false,
+"unsorted": false,
+"sorted": true
+},
+"numberOfElements": 2,
+"empty": false
+}
|
+
+
+ | User | 매니저 권한 부여 | PATCH | /v1/users/{userId}/authority |
+ |
+ {
+"id": 1,
+"username": "test01",
+"password": "$2a$10$OZnji3urkIVAufCRiwIu8e4t.JWZUZMSe.dM4qAu9r95A7qzkpFN2",
+"nickname": "테스트입니다",
+"authority": "관리자"
+}
|
+